summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp160
-rw-r--r--Android.bp1
-rw-r--r--ProtoLibraries.bp1
-rw-r--r--Ravenwood.bp1
-rw-r--r--STABILITY_OWNERS2
-rw-r--r--TEST_MAPPING16
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java2
-rw-r--r--api/Android.bp33
-rw-r--r--boot/Android.bp10
-rw-r--r--cmds/idmap2/libidmap2/FabricatedOverlay.cpp4
-rw-r--r--cmds/idmap2/libidmap2/ResourceContainer.cpp15
-rw-r--r--core/api/current.txt56
-rw-r--r--core/api/system-current.txt32
-rw-r--r--core/api/test-current.txt57
-rw-r--r--core/java/android/adaptiveauth/OWNERS1
-rw-r--r--core/java/android/adaptiveauth/flags.aconfig8
-rw-r--r--core/java/android/app/ActivityManager.java49
-rw-r--r--core/java/android/app/AppOpsManager.java24
-rw-r--r--core/java/android/app/ApplicationStartInfo.java105
-rw-r--r--core/java/android/app/AutomaticZenRule.java69
-rw-r--r--core/java/android/app/GrammaticalInflectionManager.java5
-rw-r--r--core/java/android/app/IActivityManager.aidl1
-rw-r--r--core/java/android/app/IGrammaticalInflectionManager.aidl21
-rw-r--r--core/java/android/app/IUiModeManager.aidl28
-rw-r--r--core/java/android/app/Notification.java2
-rw-r--r--core/java/android/app/SystemServiceRegistry.java4
-rw-r--r--core/java/android/app/UiModeManager.java104
-rw-r--r--core/java/android/app/activity_manager.aconfig9
-rw-r--r--core/java/android/app/ambientcontext/AmbientContextEvent.java11
-rw-r--r--core/java/android/app/usage/UsageEventsQuery.java51
-rw-r--r--core/java/android/app/usage/UsageStatsManager.java5
-rw-r--r--core/java/android/companion/CompanionDeviceManager.java65
-rw-r--r--core/java/android/content/Context.java13
-rw-r--r--core/java/android/content/pm/IPackageInstaller.aidl2
-rw-r--r--core/java/android/content/pm/PackageInstaller.java6
-rw-r--r--core/java/android/content/pm/PackageManager.java7
-rw-r--r--core/java/android/content/pm/UserProperties.java39
-rw-r--r--core/java/android/content/pm/multiuser.aconfig7
-rw-r--r--core/java/android/content/res/FontScaleConverter.java33
-rw-r--r--core/java/android/content/res/FontScaleConverterFactory.java6
-rw-r--r--core/java/android/credentials/flags.aconfig14
-rw-r--r--core/java/android/hardware/biometrics/BiometricPrompt.java70
-rw-r--r--core/java/android/hardware/biometrics/PromptContentItem.java (renamed from core/java/android/hardware/biometrics/PromptContentListItem.java)4
-rw-r--r--core/java/android/hardware/biometrics/PromptContentItemBulletedText.java (renamed from core/java/android/hardware/biometrics/PromptContentListItemBulletedText.java)14
-rw-r--r--core/java/android/hardware/biometrics/PromptContentItemParcelable.java (renamed from core/java/android/hardware/biometrics/PromptContentListItemParcelable.java)6
-rw-r--r--core/java/android/hardware/biometrics/PromptContentItemPlainText.java (renamed from core/java/android/hardware/biometrics/PromptContentListItemPlainText.java)14
-rw-r--r--core/java/android/hardware/biometrics/PromptInfo.java44
-rw-r--r--core/java/android/hardware/biometrics/PromptVerticalListContentView.java60
-rw-r--r--core/java/android/hardware/radio/TEST_MAPPING7
-rw-r--r--core/java/android/os/BatteryManager.java87
-rw-r--r--core/java/android/os/BatteryProperty.java19
-rw-r--r--core/java/android/os/flags.aconfig8
-rw-r--r--core/java/android/permission/flags.aconfig3
-rw-r--r--core/java/android/provider/Settings.java51
-rw-r--r--core/java/android/provider/TEST_MAPPING5
-rw-r--r--core/java/android/security/flags.aconfig2
-rw-r--r--core/java/android/service/notification/ZenDeviceEffects.java60
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java55
-rw-r--r--core/java/android/service/notification/ZenPolicy.java74
-rw-r--r--core/java/android/service/voice/VisualQueryDetector.java6
-rw-r--r--core/java/android/telephony/PhoneStateListener.java4
-rw-r--r--core/java/android/telephony/TelephonyCallback.java69
-rw-r--r--core/java/android/telephony/TelephonyRegistryManager.java94
-rw-r--r--core/java/android/text/MeasuredParagraph.java28
-rw-r--r--core/java/android/text/TextLine.java184
-rw-r--r--core/java/android/tracing/transition/TransitionDataSource.java16
-rw-r--r--core/java/android/view/EventLogTags.logtags5
-rw-r--r--core/java/android/view/IWindowManager.aidl9
-rw-r--r--core/java/android/view/Surface.java23
-rw-r--r--core/java/android/view/SurfaceControl.java28
-rw-r--r--core/java/android/view/View.java1
-rw-r--r--core/java/android/view/ViewRootImpl.java75
-rw-r--r--core/java/android/view/accessibility/flags/accessibility_flags.aconfig7
-rw-r--r--core/java/android/window/IScreenRecordingCallback.aidl24
-rw-r--r--core/java/android/window/TransitionInfo.java39
-rw-r--r--core/java/android/window/flags/responsible_apis.aconfig7
-rw-r--r--core/java/android/window/flags/window_surfaces.aconfig8
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig13
-rw-r--r--core/java/android/window/flags/windowing_sdk.aconfig7
-rw-r--r--core/java/com/android/internal/os/MonotonicClock.java42
-rw-r--r--core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java2
-rw-r--r--core/java/com/android/internal/telephony/IPhoneStateListener.aidl1
-rw-r--r--core/java/com/android/internal/telephony/ITelephonyRegistry.aidl1
-rw-r--r--core/java/com/android/internal/util/CollectionUtils.java13
-rw-r--r--core/java/com/android/internal/util/RingBuffer.java39
-rw-r--r--core/jni/android_hardware_OverlayProperties.cpp7
-rw-r--r--core/jni/android_view_SurfaceControl.cpp10
-rw-r--r--core/res/AndroidManifest.xml39
-rw-r--r--core/res/res/drawable-nodpi/platlogo.xml220
-rw-r--r--core/res/res/values/attrs.xml8
-rw-r--r--core/res/res/values/attrs_manifest.xml6
-rw-r--r--core/res/res/values/config.xml2
-rw-r--r--core/res/res/values/config_telephony.xml9
-rw-r--r--core/res/res/values/public-staging.xml2
-rw-r--r--core/res/res/values/strings.xml6
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/BroadcastRadioTests/TEST_MAPPING7
-rw-r--r--core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java54
-rw-r--r--core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java179
-rw-r--r--core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java171
-rw-r--r--core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java54
-rw-r--r--core/tests/coretests/Android.bp1
-rw-r--r--core/tests/coretests/src/android/app/AutomaticZenRuleTest.java66
-rw-r--r--core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java25
-rw-r--r--core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt6
-rw-r--r--core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt224
-rw-r--r--core/tests/coretests/src/android/view/ViewRootImplTest.java6
-rw-r--r--core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java48
-rw-r--r--data/etc/com.android.documentsui.xml2
-rw-r--r--data/etc/privapp-permissions-platform.xml4
-rw-r--r--data/etc/services.core.protolog.json12
-rw-r--r--data/fonts/Android.bp27
-rw-r--r--data/fonts/font_fallback_cjkvf.xml1015
-rw-r--r--data/fonts/fonts_cjkvf.xml1785
-rw-r--r--graphics/java/android/graphics/Paint.java169
-rw-r--r--graphics/java/android/graphics/text/LineBreakConfig.java35
-rw-r--r--graphics/java/android/graphics/text/MeasuredText.java4
-rw-r--r--keystore/java/android/security/Authorization.java6
-rw-r--r--libs/WindowManager/Shell/Android.bp1
-rw-r--r--libs/WindowManager/Shell/aconfig/multitasking.aconfig7
-rw-r--r--libs/WindowManager/Shell/res/values/config.xml7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java41
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java)58
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java174
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java12
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java43
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt44
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt11
-rw-r--r--libs/androidfw/LoadedArsc.cpp25
-rw-r--r--libs/androidfw/ResourceTypes.cpp70
-rw-r--r--libs/androidfw/include/androidfw/Idmap.h2
-rw-r--r--libs/androidfw/include/androidfw/LoadedArsc.h7
-rw-r--r--libs/androidfw/include/androidfw/ResourceTypes.h48
-rw-r--r--libs/hwui/Android.bp1
-rw-r--r--libs/hwui/DeviceInfo.h9
-rw-r--r--libs/hwui/FeatureFlags.h8
-rw-r--r--libs/hwui/HardwareBitmapUploader.cpp2
-rw-r--r--libs/hwui/Properties.cpp5
-rw-r--r--libs/hwui/Properties.h1
-rw-r--r--libs/hwui/hwui/MinikinSkia.cpp10
-rw-r--r--libs/hwui/hwui/MinikinUtils.cpp10
-rw-r--r--libs/hwui/hwui/Paint.h5
-rw-r--r--libs/hwui/hwui/PaintImpl.cpp36
-rw-r--r--libs/hwui/jni/android_graphics_Canvas.cpp26
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp3
-rw-r--r--libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h3
-rw-r--r--libs/hwui/pipeline/skia/SkiaPipeline.cpp20
-rw-r--r--libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp3
-rw-r--r--libs/hwui/pipeline/skia/SkiaVulkanPipeline.h3
-rw-r--r--libs/hwui/renderthread/CacheManager.cpp31
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp23
-rw-r--r--libs/hwui/renderthread/CanvasContext.h6
-rw-r--r--libs/hwui/renderthread/EglManager.cpp14
-rw-r--r--libs/hwui/renderthread/IRenderPipeline.h3
-rw-r--r--media/java/android/media/AudioFormat.java2
-rw-r--r--media/java/android/media/MediaFormat.java31
-rw-r--r--media/java/android/media/MediaRouter2.java6
-rw-r--r--media/java/android/media/flags/media_better_together.aconfig7
-rw-r--r--media/java/android/media/projection/IMediaProjectionManager.aidl2
-rw-r--r--media/java/android/media/projection/MediaProjectionInfo.java20
-rw-r--r--media/java/android/media/tv/ITvInputManager.aidl3
-rw-r--r--media/java/android/media/tv/ITvInputSession.aidl3
-rw-r--r--media/java/android/media/tv/ITvInputSessionWrapper.java10
-rw-r--r--media/java/android/media/tv/TvContract.java20
-rw-r--r--media/java/android/media/tv/TvInputManager.java12
-rw-r--r--media/java/android/media/tv/TvInputService.java18
-rw-r--r--media/java/android/media/tv/TvTrackInfo.java21
-rw-r--r--media/java/android/media/tv/TvView.java17
-rw-r--r--media/java/android/media/tv/ad/ITvAdClient.aidl30
-rw-r--r--media/java/android/media/tv/ad/ITvAdManager.aidl15
-rw-r--r--media/java/android/media/tv/ad/ITvAdManagerCallback.aidl27
-rw-r--r--media/java/android/media/tv/ad/ITvAdService.aidl35
-rw-r--r--media/java/android/media/tv/ad/ITvAdServiceCallback.aidl24
-rw-r--r--media/java/android/media/tv/ad/ITvAdSession.aidl7
-rw-r--r--media/java/android/media/tv/ad/ITvAdSessionCallback.aidl29
-rw-r--r--media/java/android/media/tv/ad/ITvAdSessionWrapper.java152
-rw-r--r--media/java/android/media/tv/ad/TvAdManager.java601
-rw-r--r--media/java/android/media/tv/ad/TvAdService.java406
-rw-r--r--media/java/android/media/tv/ad/TvAdServiceInfo.java18
-rw-r--r--media/java/android/media/tv/ad/TvAdView.java243
-rw-r--r--media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl1
-rw-r--r--media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl2
-rw-r--r--media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl1
-rw-r--r--media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl1
-rw-r--r--media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java11
-rwxr-xr-xmedia/java/android/media/tv/interactive/TvInteractiveAppManager.java43
-rwxr-xr-xmedia/java/android/media/tv/interactive/TvInteractiveAppService.java51
-rwxr-xr-xmedia/java/android/media/tv/interactive/TvInteractiveAppView.java46
-rw-r--r--media/java/android/media/tv/tuner/Lnb.java36
-rw-r--r--media/java/android/media/tv/tuner/Tuner.java14
-rw-r--r--media/jni/android_media_tv_Tuner.cpp2
-rw-r--r--nfc/Android.bp2
-rw-r--r--nfc/api/current.txt1
-rw-r--r--nfc/java/android/nfc/INfcAdapter.aidl3
-rw-r--r--nfc/java/android/nfc/INfcCardEmulation.aidl1
-rw-r--r--nfc/java/android/nfc/NfcAdapter.java60
-rw-r--r--nfc/java/android/nfc/cardemulation/ApduServiceInfo.java44
-rw-r--r--nfc/java/android/nfc/cardemulation/CardEmulation.java48
-rw-r--r--nfc/lint-baseline.xml213
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java8
-rw-r--r--packages/SettingsLib/ActionBarShadow/Android.bp3
-rw-r--r--packages/SettingsLib/ActionButtonsPreference/Android.bp3
-rw-r--r--packages/SettingsLib/ActivityEmbedding/Android.bp3
-rw-r--r--packages/SettingsLib/AdaptiveIcon/Android.bp3
-rw-r--r--packages/SettingsLib/Android.bp12
-rw-r--r--packages/SettingsLib/AppPreference/Android.bp3
-rw-r--r--packages/SettingsLib/BannerMessagePreference/Android.bp3
-rw-r--r--packages/SettingsLib/BarChartPreference/Android.bp3
-rw-r--r--packages/SettingsLib/ButtonPreference/Android.bp3
-rw-r--r--packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp3
-rw-r--r--packages/SettingsLib/DisplayUtils/Android.bp3
-rw-r--r--packages/SettingsLib/EntityHeaderWidgets/Android.bp7
-rw-r--r--packages/SettingsLib/FooterPreference/Android.bp3
-rw-r--r--packages/SettingsLib/HelpUtils/Android.bp3
-rw-r--r--packages/SettingsLib/IllustrationPreference/Android.bp3
-rw-r--r--packages/SettingsLib/LayoutPreference/Android.bp3
-rw-r--r--packages/SettingsLib/MainSwitchPreference/Android.bp3
-rw-r--r--packages/SettingsLib/ProfileSelector/Android.bp3
-rw-r--r--packages/SettingsLib/RestrictedLockUtils/Android.bp3
-rw-r--r--packages/SettingsLib/SchedulesProvider/Android.bp3
-rw-r--r--packages/SettingsLib/SearchProvider/Android.bp3
-rw-r--r--packages/SettingsLib/SelectorWithWidgetPreference/Android.bp3
-rw-r--r--packages/SettingsLib/SettingsSpinner/Android.bp3
-rw-r--r--packages/SettingsLib/SettingsTransition/Android.bp3
-rw-r--r--packages/SettingsLib/Spa/build.gradle.kts2
-rw-r--r--packages/SettingsLib/Spa/gallery/AndroidManifest.xml3
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDialogActivity.kt45
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaDialogActivity.kt131
-rw-r--r--packages/SettingsLib/Spa/gradle/libs.versions.toml2
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle.kts4
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaBaseDialogActivity.kt46
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt3
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt13
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt4
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt88
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt6
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt1
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt3
-rw-r--r--packages/SettingsLib/Tile/Android.bp3
-rw-r--r--packages/SettingsLib/TopIntroPreference/Android.bp3
-rw-r--r--packages/SettingsLib/TwoTargetPreference/Android.bp3
-rw-r--r--packages/SettingsLib/UsageProgressBarPreference/Android.bp3
-rw-r--r--packages/SettingsLib/Utils/Android.bp3
-rw-r--r--packages/SettingsLib/search/Android.bp3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java1
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java9
-rw-r--r--packages/SettingsProvider/TEST_MAPPING5
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java2
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java5
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java20
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java56
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig8
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java35
-rw-r--r--packages/Shell/AndroidManifest.xml7
-rw-r--r--packages/SystemUI/AndroidManifest.xml2
-rw-r--r--packages/SystemUI/aconfig/accessibility.aconfig7
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig14
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt3
-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/ContentListState.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt92
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/LazyGridStateExt.kt47
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/ModifierExt.kt28
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt54
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt34
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt56
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt2
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt8
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt45
-rw-r--r--packages/SystemUI/compose/features/tests/AndroidManifest.xml5
-rw-r--r--packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt17
-rw-r--r--packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt25
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt83
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt41
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt93
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt5
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt75
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt57
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt58
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt36
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt29
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt70
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt135
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt145
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt195
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt95
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt87
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt59
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt8
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt100
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt27
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt1331
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt40
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt13
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt90
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt27
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt26
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt72
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt74
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt57
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt12
-rw-r--r--packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml27
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml2
-rw-r--r--packages/SystemUI/res-keyguard/values/dimens.xml6
-rw-r--r--packages/SystemUI/res-keyguard/values/strings.xml4
-rw-r--r--packages/SystemUI/res-keyguard/values/styles.xml1
-rw-r--r--packages/SystemUI/res/drawable/ic_satellite_connected_0.xml49
-rw-r--r--packages/SystemUI/res/drawable/ic_satellite_connected_1.xml49
-rw-r--r--packages/SystemUI/res/drawable/ic_satellite_connected_2.xml47
-rw-r--r--packages/SystemUI/res/drawable/ic_satellite_not_connected.xml42
-rw-r--r--packages/SystemUI/res/layout/bindable_status_bar_icon.xml34
-rw-r--r--packages/SystemUI/res/layout/biometric_prompt_layout.xml7
-rw-r--r--packages/SystemUI/res/layout/bluetooth_tile_dialog.xml32
-rw-r--r--packages/SystemUI/res/layout/media_session_view.xml10
-rw-r--r--packages/SystemUI/res/layout/qs_footer_impl.xml4
-rw-r--r--packages/SystemUI/res/values-night/colors.xml2
-rw-r--r--packages/SystemUI/res/values-sw720dp-land/config.xml3
-rw-r--r--packages/SystemUI/res/values/colors.xml2
-rw-r--r--packages/SystemUI/res/values/dimens.xml6
-rw-r--r--packages/SystemUI/res/values/ids.xml3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java11
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java15
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java42
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt108
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt87
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt76
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt154
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt92
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt55
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt33
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt131
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInLayer.kt73
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt203
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt49
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeSmartspaceSection.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/OemSatelliteInputLog.kt)9
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/LeftRightArrowPressedListener.kt75
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java65
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt51
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java409
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncGroupHeaderViewInflation.kt53
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java67
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt69
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/DeviceBasedSatelliteBindableIcon.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/binder/DeviceBasedSatelliteIconBinder.kt50
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconView.kt184
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java393
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt115
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt211
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt107
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java227
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java95
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt110
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt150
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java184
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/ui/viewmodel/KeyguardStatusBarViewModelTest.kt14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java20
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt16
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt24
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt5
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryKosmos.kt23
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt32
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt15
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt9
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorFactory.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt7
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt13
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt73
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt186
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt90
-rw-r--r--ravenwood/junit-src/android/platform/test/annotations/ExcludeUnderRavenwood.java44
-rw-r--r--ravenwood/junit-src/android/platform/test/annotations/IncludeUnderRavenwood.java44
-rw-r--r--ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java68
-rw-r--r--services/autofill/features.aconfig7
-rw-r--r--services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java4
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java30
-rw-r--r--services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java23
-rw-r--r--services/companion/java/com/android/server/companion/transport/SecureTransport.java2
-rw-r--r--services/core/Android.bp8
-rw-r--r--services/core/java/com/android/server/BatteryService.java2
-rw-r--r--services/core/java/com/android/server/BootReceiver.java83
-rw-r--r--services/core/java/com/android/server/SensitiveContentProtectionManagerService.java126
-rw-r--r--services/core/java/com/android/server/StorageManagerService.java1
-rw-r--r--services/core/java/com/android/server/TelephonyRegistry.java99
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java63
-rw-r--r--services/core/java/com/android/server/Watchdog.java1
-rw-r--r--services/core/java/com/android/server/ZramWriteback.java1
-rw-r--r--services/core/java/com/android/server/adaptiveauth/OWNERS1
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java4
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java7
-rw-r--r--services/core/java/com/android/server/am/AppStartInfoTracker.java42
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java65
-rw-r--r--services/core/java/com/android/server/app/GameManagerSettings.java1
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java107
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java31
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java2
-rw-r--r--services/core/java/com/android/server/biometrics/AuthService.java9
-rw-r--r--services/core/java/com/android/server/broadcastradio/TEST_MAPPING7
-rw-r--r--services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java10
-rw-r--r--services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java48
-rw-r--r--services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java11
-rw-r--r--services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionUtils.java46
-rwxr-xr-xservices/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java9
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecController.java16
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java22
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java2
-rw-r--r--services/core/java/com/android/server/hdmi/NewDeviceAction.java61
-rw-r--r--services/core/java/com/android/server/health/HealthServiceWrapperAidl.java14
-rw-r--r--services/core/java/com/android/server/input/KeyboardLayoutManager.java2
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java2
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java2
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java17
-rw-r--r--services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java6
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java9
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyLogger.java9
-rw-r--r--services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java10
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java3
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java165
-rw-r--r--services/core/java/com/android/server/os/NativeTombstoneManager.java135
-rw-r--r--services/core/java/com/android/server/pm/BackgroundInstallControlService.java8
-rw-r--r--services/core/java/com/android/server/pm/DataLoaderManagerService.java7
-rw-r--r--services/core/java/com/android/server/pm/IPackageManagerBase.java5
-rw-r--r--services/core/java/com/android/server/pm/LauncherAppsService.java16
-rw-r--r--services/core/java/com/android/server/pm/PackageArchiver.java66
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java20
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java3
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java2
-rw-r--r--services/core/java/com/android/server/pm/ProtectedPackages.java5
-rw-r--r--services/core/java/com/android/server/pm/RemovePackageHelper.java7
-rw-r--r--services/core/java/com/android/server/pm/Settings.java19
-rw-r--r--services/core/java/com/android/server/pm/ShortcutNonPersistentUser.java4
-rw-r--r--services/core/java/com/android/server/pm/ShortcutPackage.java25
-rw-r--r--services/core/java/com/android/server/pm/ShortcutService.java22
-rw-r--r--services/core/java/com/android/server/pm/UserDataPreparer.java2
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java251
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java7
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java7
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java4
-rw-r--r--services/core/java/com/android/server/recoverysystem/RecoverySystemService.java7
-rw-r--r--services/core/java/com/android/server/trust/TrustManagerService.java78
-rw-r--r--services/core/java/com/android/server/tv/TvInputManagerService.java20
-rw-r--r--services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java886
-rw-r--r--services/core/java/com/android/server/utils/AnrTimer.java2
-rw-r--r--services/core/java/com/android/server/utils/flags.aconfig2
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java2
-rw-r--r--services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java11
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java1
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartInterceptor.java17
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java4
-rw-r--r--services/core/java/com/android/server/wm/BackgroundActivityStartController.java126
-rw-r--r--services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java284
-rw-r--r--services/core/java/com/android/server/wm/SensitiveContentPackages.java30
-rw-r--r--services/core/java/com/android/server/wm/Task.java4
-rw-r--r--services/core/java/com/android/server/wm/Transition.java13
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java9
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerInternal.java11
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java39
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java2
-rw-r--r--services/core/jni/Android.bp11
-rw-r--r--services/core/jni/OWNERS4
-rw-r--r--services/core/jni/com_android_server_BootReceiver.cpp57
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp2
-rw-r--r--services/core/jni/onload.cpp2
-rw-r--r--services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt9
-rw-r--r--services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt9
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt17
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java85
-rw-r--r--services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java17
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java375
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java587
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java22
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java26
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java161
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java206
-rw-r--r--services/tests/servicestests/Android.bp3
-rw-r--r--services/tests/servicestests/AndroidTest.xml1
-rw-r--r--services/tests/servicestests/src/com/android/server/BootReceiverTest.java97
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java233
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java55
-rw-r--r--services/tests/servicestests/test-apps/JobTestApp/Android.bp37
-rw-r--r--services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml27
-rw-r--r--services/tests/servicestests/test-apps/JobTestApp/OWNERS1
-rw-r--r--services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java67
-rw-r--r--services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java53
-rw-r--r--services/tests/uiservicestests/Android.bp1
-rw-r--r--services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java59
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java44
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java5
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java4
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java40
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java6
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java407
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java29
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java103
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java61
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java4
-rw-r--r--services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java20
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java2
-rw-r--r--services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java3
-rw-r--r--telecomm/java/android/telecom/TelecomManager.java11
-rw-r--r--telephony/common/com/android/internal/telephony/TelephonyPermissions.java3
-rw-r--r--telephony/java/android/telephony/CarrierConfigManager.java23
-rw-r--r--telephony/java/android/telephony/SecurityAlgorithmUpdate.java35
-rw-r--r--telephony/java/android/telephony/TelephonyManager.java12
-rw-r--r--telephony/java/android/telephony/ims/ImsService.java28
-rw-r--r--telephony/java/android/telephony/satellite/SatelliteManager.java11
-rw-r--r--test-base/Android.bp3
-rw-r--r--test-mock/Android.bp3
-rw-r--r--test-runner/Android.bp3
-rw-r--r--tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt22
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java2
-rw-r--r--tests/testables/src/android/testing/TestableContext.java43
-rw-r--r--tests/testables/src/android/testing/TestableLooper.java97
-rw-r--r--tools/aapt2/format/binary/TableFlattener.cpp17
-rw-r--r--tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java36
678 files changed, 20869 insertions, 7142 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 2ae72ef4e81c..f5bf437a738d 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -13,70 +13,143 @@
// limitations under the License.
aconfig_srcjars = [
- ":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+ // !!! KEEP THIS LIST ALPHABETICAL !!!
+ ":aconfig_mediacodec_flags_java_lib{.generated_srcjars}",
+ ":android.adaptiveauth.flags-aconfig-java{.generated_srcjars}",
+ ":android.app.flags-aconfig-java{.generated_srcjars}",
":android.app.smartspace.flags-aconfig-java{.generated_srcjars}",
+ ":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+ ":android.appwidget.flags-aconfig-java{.generated_srcjars}",
+ ":android.chre.flags-aconfig-java{.generated_srcjars}",
":android.companion.flags-aconfig-java{.generated_srcjars}",
+ ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
+ ":android.content.flags-aconfig-java{.generated_srcjars}",
":android.content.pm.flags-aconfig-java{.generated_srcjars}",
":android.content.res.flags-aconfig-java{.generated_srcjars}",
+ ":android.credentials.flags-aconfig-java{.generated_srcjars}",
+ ":android.database.sqlite-aconfig-java{.generated_srcjars}",
+ ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}",
":android.hardware.flags-aconfig-java{.generated_srcjars}",
":android.hardware.radio.flags-aconfig-java{.generated_srcjars}",
+ ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
":android.location.flags-aconfig-java{.generated_srcjars}",
+ ":android.media.tv.flags-aconfig-java{.generated_srcjars}",
+ ":android.multiuser.flags-aconfig-java{.generated_srcjars}",
":android.net.vcn.flags-aconfig-java{.generated_srcjars}",
":android.nfc.flags-aconfig-java{.generated_srcjars}",
":android.os.flags-aconfig-java{.generated_srcjars}",
":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
+ ":android.permission.flags-aconfig-java{.generated_srcjars}",
+ ":android.provider.flags-aconfig-java{.generated_srcjars}",
":android.security.flags-aconfig-java{.generated_srcjars}",
":android.server.app.flags-aconfig-java{.generated_srcjars}",
+ ":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
":android.service.chooser.flags-aconfig-java{.generated_srcjars}",
+ ":android.service.controls.flags-aconfig-java{.generated_srcjars}",
":android.service.dreams.flags-aconfig-java{.generated_srcjars}",
":android.service.notification.flags-aconfig-java{.generated_srcjars}",
- ":android.view.flags-aconfig-java{.generated_srcjars}",
+ ":android.service.voice.flags-aconfig-java{.generated_srcjars}",
+ ":android.speech.flags-aconfig-java{.generated_srcjars}",
+ ":android.tracing.flags-aconfig-java{.generated_srcjars}",
":android.view.accessibility.flags-aconfig-java{.generated_srcjars}",
+ ":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}",
+ ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
+ ":android.view.flags-aconfig-java{.generated_srcjars}",
+ ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",
+ ":android.webkit.flags-aconfig-java{.generated_srcjars}",
+ ":android.widget.flags-aconfig-java{.generated_srcjars}",
":audio-framework-aconfig",
":camera_platform_flags_core_java_lib{.generated_srcjars}",
- ":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
- ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}",
":com.android.hardware.input-aconfig-java{.generated_srcjars}",
":com.android.input.flags-aconfig-java{.generated_srcjars}",
- ":com.android.text.flags-aconfig-java{.generated_srcjars}",
- ":framework-jobscheduler-job.flags-aconfig-java{.generated_srcjars}",
- ":telecom_flags_core_java_lib{.generated_srcjars}",
- ":telephony_flags_core_java_lib{.generated_srcjars}",
- ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
- ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",
- ":android.widget.flags-aconfig-java{.generated_srcjars}",
- ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
- ":sdk_sandbox_flags_lib{.generated_srcjars}",
- ":android.permission.flags-aconfig-java{.generated_srcjars}",
- ":android.database.sqlite-aconfig-java{.generated_srcjars}",
- ":hwui_flags_java_lib{.generated_srcjars}",
- ":framework_graphics_flags_java_lib{.generated_srcjars}",
- ":display_flags_lib{.generated_srcjars}",
":com.android.internal.foldables.flags-aconfig-java{.generated_srcjars}",
- ":android.multiuser.flags-aconfig-java{.generated_srcjars}",
- ":android.app.flags-aconfig-java{.generated_srcjars}",
- ":android.credentials.flags-aconfig-java{.generated_srcjars}",
- ":android.view.contentprotection.flags-aconfig-java{.generated_srcjars}",
- ":com.android.server.flags.services-aconfig-java{.generated_srcjars}",
- ":android.service.controls.flags-aconfig-java{.generated_srcjars}",
- ":android.service.voice.flags-aconfig-java{.generated_srcjars}",
- ":android.media.tv.flags-aconfig-java{.generated_srcjars}",
- ":android.service.autofill.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
":com.android.net.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.server.flags.services-aconfig-java{.generated_srcjars}",
+ ":com.android.text.flags-aconfig-java{.generated_srcjars}",
+ ":com.android.window.flags.window-aconfig-java{.generated_srcjars}",
":device_policy_aconfig_flags_lib{.generated_srcjars}",
- ":surfaceflinger_flags_java_lib{.generated_srcjars}",
- ":android.view.contentcapture.flags-aconfig-java{.generated_srcjars}",
- ":android.hardware.usb.flags-aconfig-java{.generated_srcjars}",
- ":android.tracing.flags-aconfig-java{.generated_srcjars}",
- ":android.appwidget.flags-aconfig-java{.generated_srcjars}",
- ":android.webkit.flags-aconfig-java{.generated_srcjars}",
- ":android.provider.flags-aconfig-java{.generated_srcjars}",
- ":android.chre.flags-aconfig-java{.generated_srcjars}",
- ":android.speech.flags-aconfig-java{.generated_srcjars}",
+ ":display_flags_lib{.generated_srcjars}",
+ ":framework-jobscheduler-job.flags-aconfig-java{.generated_srcjars}",
+ ":framework_graphics_flags_java_lib{.generated_srcjars}",
+ ":hwui_flags_java_lib{.generated_srcjars}",
":power_flags_lib{.generated_srcjars}",
- ":android.content.flags-aconfig-java{.generated_srcjars}",
+ ":sdk_sandbox_flags_lib{.generated_srcjars}",
+ ":surfaceflinger_flags_java_lib{.generated_srcjars}",
+ ":telecom_flags_core_java_lib{.generated_srcjars}",
+ ":telephony_flags_core_java_lib{.generated_srcjars}",
+ // !!! KEEP THIS LIST ALPHABETICAL !!!
]
+stubs_defaults {
+ name: "framework-minus-apex-aconfig-declarations",
+ aconfig_declarations: [
+ "android.app.flags-aconfig",
+ "android.app.smartspace.flags-aconfig",
+ "android.app.usage.flags-aconfig",
+ "android.appwidget.flags-aconfig",
+ "android.companion.flags-aconfig",
+ "android.companion.virtual.flags-aconfig",
+ "android.content.pm.flags-aconfig",
+ "android.content.res.flags-aconfig",
+ "android.credentials.flags-aconfig",
+ "android.database.sqlite-aconfig",
+ "android.hardware.biometrics.flags-aconfig",
+ "android.hardware.flags-aconfig",
+ "android.hardware.radio.flags-aconfig",
+ "android.hardware.usb.flags-aconfig",
+ "android.location.flags-aconfig",
+ "android.media.audio-aconfig",
+ "android.media.audiopolicy-aconfig",
+ "android.media.midi-aconfig",
+ "android.media.tv.flags-aconfig",
+ "android.multiuser.flags-aconfig",
+ "android.net.vcn.flags-aconfig",
+ "android.nfc.flags-aconfig",
+ "android.os.flags-aconfig",
+ "android.os.vibrator.flags-aconfig",
+ "android.permission.flags-aconfig",
+ "android.provider.flags-aconfig",
+ "android.security.flags-aconfig",
+ "android.server.app.flags-aconfig",
+ "android.service.autofill.flags-aconfig",
+ "android.service.chooser.flags-aconfig",
+ "android.service.controls.flags-aconfig",
+ "android.service.dreams.flags-aconfig",
+ "android.service.notification.flags-aconfig",
+ "android.service.voice.flags-aconfig",
+ "android.speech.flags-aconfig",
+ "android.tracing.flags-aconfig",
+ "android.view.accessibility.flags-aconfig",
+ "android.view.contentcapture.flags-aconfig",
+ "android.view.contentprotection.flags-aconfig",
+ "android.view.flags-aconfig",
+ "android.view.inputmethod.flags-aconfig",
+ "android.webkit.flags-aconfig",
+ "android.widget.flags-aconfig",
+ "camera_platform_flags",
+ "chre_flags",
+ "com.android.hardware.input.input-aconfig",
+ "com.android.input.flags-aconfig",
+ "com.android.media.flags.bettertogether-aconfig",
+ "com.android.net.flags-aconfig",
+ "com.android.server.flags.services-aconfig",
+ "com.android.text.flags-aconfig",
+ "com.android.window.flags.window-aconfig",
+ "device_policy_aconfig_flags",
+ "display_flags",
+ "fold_lock_setting_flags",
+ "framework-jobscheduler-job.flags-aconfig",
+ "framework_graphics_flags",
+ "hwui_flags",
+ "power_flags",
+ "sdk_sandbox_flags",
+ "surfaceflinger_flags",
+ "telecom_flags",
+ "telephony_flags",
+ ],
+}
+
filegroup {
name: "framework-minus-apex-aconfig-srcjars",
srcs: aconfig_srcjars,
@@ -961,3 +1034,16 @@ java_aconfig_library {
aconfig_declarations: "android.content.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// Adaptive Auth
+aconfig_declarations {
+ name: "android.adaptiveauth.flags-aconfig",
+ package: "android.adaptiveauth",
+ srcs: ["core/java/android/adaptiveauth/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.adaptiveauth.flags-aconfig-java",
+ aconfig_declarations: "android.adaptiveauth.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/Android.bp b/Android.bp
index 485f1be42017..9c56733650cb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -216,7 +216,6 @@ java_library {
"apex_aidl_interface-java",
"packagemanager_aidl-java",
"framework-protos",
- "libtombstone_proto_java",
"updatable-driver-protos",
"ota_metadata_proto_java",
"android.hidl.base-V1.0-java",
diff --git a/ProtoLibraries.bp b/ProtoLibraries.bp
index d03bbd249b00..e7adf203334e 100644
--- a/ProtoLibraries.bp
+++ b/ProtoLibraries.bp
@@ -34,6 +34,7 @@ gensrcs {
":ipconnectivity-proto-src",
":libstats_atom_enum_protos",
":libstats_atom_message_protos",
+ ":libtombstone_proto-src",
"core/proto/**/*.proto",
"libs/incident/**/*.proto",
],
diff --git a/Ravenwood.bp b/Ravenwood.bp
index d13c4d78190c..0877bcedb609 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -97,6 +97,7 @@ android_ravenwood_libgroup {
"framework-minus-apex.ravenwood",
"hoststubgen-helper-runtime.ravenwood",
"hoststubgen-helper-framework-runtime.ravenwood",
+ "core-libart-for-host",
"all-updatable-modules-system-stubs",
"junit",
"truth",
diff --git a/STABILITY_OWNERS b/STABILITY_OWNERS
deleted file mode 100644
index a7ecb4dfdd44..000000000000
--- a/STABILITY_OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-gaillard@google.com
-
diff --git a/TEST_MAPPING b/TEST_MAPPING
index d59775f4060b..c904eb46d88e 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -138,15 +138,13 @@
}
],
"postsubmit-ravenwood": [
- // TODO(ravenwood) promote it to presubmit
- // TODO: Enable it once the infra knows how to run it.
-// {
-// "name": "CtsUtilTestCasesRavenwood",
-// "file_patterns": [
-// "*Ravenwood*",
-// "*ravenwood*"
-// ]
-// }
+ {
+ "name": "CtsUtilTestCasesRavenwood",
+ "host": true,
+ "file_patterns": [
+ "[Rr]avenwood"
+ ]
+ }
],
"postsubmit-managedprofile-stress": [
{
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 2c9af67ecdc4..44afbe6aff51 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -951,7 +951,7 @@ public final class FlexibilityController extends StateController {
@VisibleForTesting
static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS;
@VisibleForTesting
- static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 72 * HOUR_IN_MILLIS;
+ static final long DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS = 24 * HOUR_IN_MILLIS;
private static final long DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS = MINUTE_IN_MILLIS;
@VisibleForTesting
final int[] DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS = {50, 60, 70, 80};
diff --git a/api/Android.bp b/api/Android.bp
index 1686943d08ca..b3b18b66e097 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -60,8 +60,40 @@ metalava_cmd = "$(location metalava)"
metalava_cmd += " -J--add-opens=java.base/java.util=ALL-UNNAMED "
metalava_cmd += " --quiet "
+soong_config_module_type {
+ name: "enable_crashrecovery_module",
+ module_type: "combined_apis_defaults",
+ config_namespace: "ANDROID",
+ bool_variables: ["release_crashrecovery_module"],
+ properties: [
+ "bootclasspath",
+ "system_server_classpath",
+ ],
+}
+
+soong_config_bool_variable {
+ name: "release_crashrecovery_module",
+}
+
+enable_crashrecovery_module {
+ name: "crashrecovery_module_defaults",
+ soong_config_variables: {
+ release_crashrecovery_module: {
+ bootclasspath: [
+ "framework-crashrecovery",
+ ],
+ system_server_classpath: [
+ "service-crashrecovery",
+ ],
+ },
+ },
+}
+
combined_apis {
name: "frameworks-base-api",
+ defaults: [
+ "crashrecovery_module_defaults",
+ ],
bootclasspath: [
"android.net.ipsec.ike",
"art.module.public.api",
@@ -269,6 +301,7 @@ packages_to_document = [
// classpath (or sources) somehow.
stubs_defaults {
name: "android-non-updatable-stubs-defaults",
+ defaults: ["framework-minus-apex-aconfig-declarations"],
srcs: [":android-non-updatable-stub-sources"],
sdk_version: "none",
system_modules: "none",
diff --git a/boot/Android.bp b/boot/Android.bp
index c4a1139b70a4..228d060bf9cf 100644
--- a/boot/Android.bp
+++ b/boot/Android.bp
@@ -30,6 +30,7 @@ soong_config_module_type {
bool_variables: [
"car_bootclasspath_fragment",
"nfc_apex_bootclasspath_fragment",
+ "release_crashrecovery_module",
],
properties: [
"fragments",
@@ -165,6 +166,15 @@ custom_platform_bootclasspath {
},
],
},
+ release_crashrecovery_module: {
+ fragments: [
+ // only used when crashrecovery is enabled
+ {
+ apex: "com.android.crashrecovery",
+ module: "com.android.crashrecovery-bootclasspath-fragment",
+ },
+ ],
+ },
},
// Additional information needed by hidden api processing.
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index 16bb896e939c..55ea15d0cdf1 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -468,9 +468,9 @@ Result<OverlayData> FabContainer::GetOverlayData(const OverlayManifestInfo& info
entry.name().c_str());
const auto& res_value = entry.res_value();
result.pairs.emplace_back(OverlayData::Value{
- name, TargetValueWithConfig{.config = entry.configuration(), .value = TargetValue{
+ name, TargetValueWithConfig{.value = TargetValue{
.data_type = static_cast<uint8_t>(res_value.data_type()),
- .data_value = res_value.data_value()}}});
+ .data_value = res_value.data_value()}, .config = entry.configuration()}});
}
}
}
diff --git a/cmds/idmap2/libidmap2/ResourceContainer.cpp b/cmds/idmap2/libidmap2/ResourceContainer.cpp
index 7869fbdb8cea..3c0e118bbfe7 100644
--- a/cmds/idmap2/libidmap2/ResourceContainer.cpp
+++ b/cmds/idmap2/libidmap2/ResourceContainer.cpp
@@ -227,9 +227,9 @@ Result<OverlayData> CreateResourceMapping(ResourceId id, const ZipAssetsProvider
} else {
overlay_data.pairs.emplace_back(
OverlayData::Value{*target_resource, TargetValueWithConfig{
- .config = std::string(),
.value = TargetValue{.data_type = overlay_resource->dataType,
- .data_value = overlay_resource->data}}});
+ .data_value = overlay_resource->data},
+ .config = std::string()}});
}
}
@@ -268,10 +268,11 @@ struct ResState {
std::unique_ptr<AssetManager2> am;
ZipAssetsProvider* zip_assets;
- static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider> zip) {
+ static Result<ResState> Initialize(std::unique_ptr<ZipAssetsProvider> zip,
+ package_property_t flags) {
ResState state;
state.zip_assets = zip.get();
- if ((state.apk_assets = ApkAssets::Load(std::move(zip))) == nullptr) {
+ if ((state.apk_assets = ApkAssets::Load(std::move(zip), flags)) == nullptr) {
return Error("failed to load apk asset");
}
@@ -284,7 +285,7 @@ struct ResState {
}
state.am = std::make_unique<AssetManager2>();
- if (!state.am->SetApkAssets({state.apk_assets})) {
+ if (!state.am->SetApkAssets({state.apk_assets}, false)) {
return Error("failed to create asset manager");
}
@@ -343,8 +344,8 @@ Result<const ResState*> ApkResourceContainer::GetState() const {
return state;
}
- auto state =
- ResState::Initialize(std::move(std::get<std::unique_ptr<ZipAssetsProvider>>(state_)));
+ auto state = ResState::Initialize(std::move(std::get<std::unique_ptr<ZipAssetsProvider>>(state_)),
+ PROPERTY_OPTIMIZE_NAME_LOOKUPS);
if (!state) {
return state.GetError();
}
diff --git a/core/api/current.txt b/core/api/current.txt
index 15c054f0b5e8..40dcc427732b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5317,7 +5317,6 @@ package android.app {
ctor @Deprecated public AutomaticZenRule(String, android.content.ComponentName, android.net.Uri, int, boolean);
ctor public AutomaticZenRule(@NonNull String, @Nullable android.content.ComponentName, @Nullable android.content.ComponentName, @NonNull android.net.Uri, @Nullable android.service.notification.ZenPolicy, int, boolean);
ctor public AutomaticZenRule(android.os.Parcel);
- method @FlaggedApi("android.app.modes_api") public boolean canUpdate();
method public int describeContents();
method public android.net.Uri getConditionId();
method @Nullable public android.content.ComponentName getConfigurationActivity();
@@ -9380,15 +9379,15 @@ package android.app.usage {
method public int describeContents();
method public long getBeginTimeMillis();
method public long getEndTimeMillis();
- method @NonNull public java.util.Set<java.lang.Integer> getEventTypes();
+ method @NonNull public int[] getEventTypes();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.usage.UsageEventsQuery> CREATOR;
}
public static final class UsageEventsQuery.Builder {
ctor public UsageEventsQuery.Builder(long, long);
- method @NonNull public android.app.usage.UsageEventsQuery.Builder addEventTypes(@NonNull int...);
method @NonNull public android.app.usage.UsageEventsQuery build();
+ method @NonNull public android.app.usage.UsageEventsQuery.Builder setEventTypes(@NonNull int...);
}
public final class UsageStats implements android.os.Parcelable {
@@ -9415,7 +9414,7 @@ package android.app.usage {
method public java.util.List<android.app.usage.ConfigurationStats> queryConfigurations(int, long, long);
method public java.util.List<android.app.usage.EventStats> queryEventStats(int, long, long);
method public android.app.usage.UsageEvents queryEvents(long, long);
- method @FlaggedApi("android.app.usage.filter_based_event_query_api") @NonNull @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public android.app.usage.UsageEvents queryEvents(@NonNull android.app.usage.UsageEventsQuery);
+ method @FlaggedApi("android.app.usage.filter_based_event_query_api") @Nullable @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public android.app.usage.UsageEvents queryEvents(@NonNull android.app.usage.UsageEventsQuery);
method public android.app.usage.UsageEvents queryEventsForSelf(long, long);
method public java.util.List<android.app.usage.UsageStats> queryUsageStats(int, long, long);
field @FlaggedApi("android.app.usage.user_interaction_type_api") public static final String EXTRA_EVENT_ACTION = "android.app.usage.extra.EVENT_ACTION";
@@ -12446,7 +12445,7 @@ package android.content.pm {
method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback);
method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback, @NonNull android.os.Handler);
method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void reportUnarchivalStatus(int, int, long, @Nullable android.app.PendingIntent) throws android.content.pm.PackageManager.NameNotFoundException;
- method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender, int) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
method @FlaggedApi("android.content.pm.archiving") @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String, @NonNull android.content.IntentSender) throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull String, @NonNull android.content.IntentSender);
method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull android.content.pm.VersionedPackage, @NonNull android.content.IntentSender);
@@ -12873,7 +12872,6 @@ package android.content.pm {
field public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3; // 0x3
field public static final int COMPONENT_ENABLED_STATE_ENABLED = 1; // 0x1
field @FlaggedApi("android.content.pm.archiving") public static final int DELETE_ARCHIVE = 16; // 0x10
- field @FlaggedApi("android.content.pm.archiving") public static final int DELETE_SHOW_DIALOG = 32; // 0x20
field public static final int DONT_KILL_APP = 1; // 0x1
field public static final String EXTRA_VERIFICATION_ID = "android.content.pm.extra.VERIFICATION_ID";
field public static final String EXTRA_VERIFICATION_RESULT = "android.content.pm.extra.VERIFICATION_RESULT";
@@ -13676,11 +13674,8 @@ package android.content.res {
@FlaggedApi("android.content.res.font_scale_converter_public") public interface FontScaleConverter {
method public float convertDpToSp(float);
method public float convertSpToDp(float);
- }
-
- @FlaggedApi("android.content.res.font_scale_converter_public") public class FontScaleConverterFactory {
- method @FlaggedApi("android.content.res.font_scale_converter_public") @AnyThread @Nullable public static android.content.res.FontScaleConverter forScale(float);
- method @FlaggedApi("android.content.res.font_scale_converter_public") @AnyThread public static boolean isNonLinearFontScalingActive(float);
+ method @AnyThread @Nullable public static android.content.res.FontScaleConverter forScale(float);
+ method @AnyThread public static boolean isNonLinearFontScalingActive(float);
}
public class ObbInfo implements android.os.Parcelable {
@@ -16400,6 +16395,8 @@ package android.graphics {
field public static final int START_HYPHEN_EDIT_NO_EDIT = 0; // 0x0
field public static final int STRIKE_THRU_TEXT_FLAG = 16; // 0x10
field public static final int SUBPIXEL_TEXT_FLAG = 128; // 0x80
+ field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000
+ field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000
field public static final int UNDERLINE_TEXT_FLAG = 8; // 0x8
}
@@ -18683,6 +18680,8 @@ package android.hardware.biometrics {
method @Nullable public int getAllowedAuthenticators();
method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable public android.hardware.biometrics.PromptContentView getContentView();
method @Nullable public CharSequence getDescription();
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @Nullable @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.graphics.Bitmap getLogoBitmap();
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @DrawableRes @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public int getLogoRes();
method @Nullable public CharSequence getNegativeButtonText();
method @Nullable public CharSequence getSubtitle();
method @NonNull public CharSequence getTitle();
@@ -18732,6 +18731,8 @@ package android.hardware.biometrics {
method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setContentView(@NonNull android.hardware.biometrics.PromptContentView);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDescription(@NonNull CharSequence);
method @Deprecated @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setDeviceCredentialAllowed(boolean);
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@DrawableRes int);
+ method @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") @NonNull @RequiresPermission("android.permission.MANAGE_BIOMETRIC_DIALOG") public android.hardware.biometrics.BiometricPrompt.Builder setLogo(@NonNull android.graphics.Bitmap);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setNegativeButton(@NonNull CharSequence, @NonNull java.util.concurrent.Executor, @NonNull android.content.DialogInterface.OnClickListener);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setSubtitle(@NonNull CharSequence);
method @NonNull public android.hardware.biometrics.BiometricPrompt.Builder setTitle(@NonNull CharSequence);
@@ -18753,21 +18754,21 @@ package android.hardware.biometrics {
method @Nullable public java.security.Signature getSignature();
}
- @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public interface PromptContentListItem {
+ @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public interface PromptContentItem {
}
- @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentListItemBulletedText implements android.os.Parcelable android.hardware.biometrics.PromptContentListItem {
- ctor public PromptContentListItemBulletedText(@NonNull CharSequence);
+ @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentItemBulletedText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem {
+ ctor public PromptContentItemBulletedText(@NonNull CharSequence);
method public int describeContents();
method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentListItemBulletedText> CREATOR;
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentItemBulletedText> CREATOR;
}
- @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentListItemPlainText implements android.os.Parcelable android.hardware.biometrics.PromptContentListItem {
- ctor public PromptContentListItemPlainText(@NonNull CharSequence);
+ @FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptContentItemPlainText implements android.os.Parcelable android.hardware.biometrics.PromptContentItem {
+ ctor public PromptContentItemPlainText(@NonNull CharSequence);
method public int describeContents();
method public void writeToParcel(@NonNull android.os.Parcel, int);
- field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentListItemPlainText> CREATOR;
+ field @NonNull public static final android.os.Parcelable.Creator<android.hardware.biometrics.PromptContentItemPlainText> CREATOR;
}
@FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public interface PromptContentView {
@@ -18776,7 +18777,7 @@ package android.hardware.biometrics {
@FlaggedApi("android.hardware.biometrics.custom_biometric_prompt") public final class PromptVerticalListContentView implements android.os.Parcelable android.hardware.biometrics.PromptContentView {
method public int describeContents();
method @Nullable public CharSequence getDescription();
- method @NonNull public java.util.List<android.hardware.biometrics.PromptContentListItem> getListItems();
+ method @NonNull public java.util.List<android.hardware.biometrics.PromptContentItem> getListItems();
method public static int getMaxEachItemCharacterNumber();
method public static int getMaxItemCount();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -18785,7 +18786,8 @@ package android.hardware.biometrics {
public static final class PromptVerticalListContentView.Builder {
ctor public PromptVerticalListContentView.Builder();
- method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentListItem);
+ method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentItem);
+ method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder addListItem(@NonNull android.hardware.biometrics.PromptContentItem, int);
method @NonNull public android.hardware.biometrics.PromptVerticalListContentView build();
method @NonNull public android.hardware.biometrics.PromptVerticalListContentView.Builder setDescription(@NonNull CharSequence);
}
@@ -23341,6 +23343,7 @@ package android.media {
field public static final String KEY_HDR10_PLUS_INFO = "hdr10-plus-info";
field public static final String KEY_HDR_STATIC_INFO = "hdr-static-info";
field public static final String KEY_HEIGHT = "height";
+ field @FlaggedApi("com.android.media.codec.flags.codec_importance") public static final String KEY_IMPORTANCE = "importance";
field public static final String KEY_INTRA_REFRESH_PERIOD = "intra-refresh-period";
field public static final String KEY_IS_ADTS = "is-adts";
field public static final String KEY_IS_AUTOSELECT = "is-autoselect";
@@ -24303,7 +24306,7 @@ package android.media {
method public void release();
method public void selectRoute(@NonNull android.media.MediaRoute2Info);
method public void setVolume(int);
- method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferRequestedBySelf();
+ method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferInitiatedBySelf();
}
public abstract static class MediaRouter2.TransferCallback {
@@ -26669,12 +26672,16 @@ package android.media.tv {
public static final class TvContract.Channels implements android.media.tv.TvContract.BaseTvColumns {
method @Nullable public static String getVideoResolution(String);
+ field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final int BROADCAST_VISIBILITY_TYPE_INVISIBLE = 2; // 0x2
+ field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final int BROADCAST_VISIBILITY_TYPE_NUMERIC_SELECTABLE_ONLY = 1; // 0x1
+ field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final int BROADCAST_VISIBILITY_TYPE_VISIBLE = 0; // 0x0
field public static final String COLUMN_APP_LINK_COLOR = "app_link_color";
field public static final String COLUMN_APP_LINK_ICON_URI = "app_link_icon_uri";
field public static final String COLUMN_APP_LINK_INTENT_URI = "app_link_intent_uri";
field public static final String COLUMN_APP_LINK_POSTER_ART_URI = "app_link_poster_art_uri";
field public static final String COLUMN_APP_LINK_TEXT = "app_link_text";
field public static final String COLUMN_BROADCAST_GENRE = "broadcast_genre";
+ field @FlaggedApi("android.media.tv.flags.broadcast_visibility_types") public static final String COLUMN_BROADCAST_VISIBILITY_TYPE = "broadcast_visibility_type";
field public static final String COLUMN_BROWSABLE = "browsable";
field public static final String COLUMN_CHANNEL_LIST_ID = "channel_list_id";
field public static final String COLUMN_DESCRIPTION = "description";
@@ -31845,6 +31852,7 @@ package android.os {
method public long computeChargeTimeRemaining();
method public int getIntProperty(int);
method public long getLongProperty(int);
+ method @FlaggedApi("android.os.battery_part_status_api") @Nullable public String getStringProperty(int);
method public boolean isCharging();
field public static final String ACTION_CHARGING = "android.os.action.CHARGING";
field public static final String ACTION_DISCHARGING = "android.os.action.DISCHARGING";
@@ -43072,6 +43080,8 @@ package android.telephony {
field public static final String KEY_RTT_UPGRADE_SUPPORTED_FOR_DOWNGRADED_VT_CALL_BOOL = "rtt_upgrade_supported_for_downgraded_vt_call";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ATTACH_SUPPORTED_BOOL = "satellite_attach_supported_bool";
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT = "satellite_connection_hysteresis_sec_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT = "satellite_entitlement_status_refresh_days_int";
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL = "satellite_entitlement_supported_bool";
field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
@@ -51409,7 +51419,7 @@ package android.view {
method @NonNull public android.view.SurfaceControl.Transaction setCrop(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect);
method @NonNull public android.view.SurfaceControl.Transaction setDamageRegion(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Region);
method @NonNull public android.view.SurfaceControl.Transaction setDataSpace(@NonNull android.view.SurfaceControl, int);
- method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.view.SurfaceControl.Transaction setDesiredPresentTime(long);
+ method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.view.SurfaceControl.Transaction setDesiredPresentTimeNanos(long);
method @NonNull public android.view.SurfaceControl.Transaction setExtendedRangeBrightness(@NonNull android.view.SurfaceControl, float, float);
method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int);
method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int, int);
@@ -51430,7 +51440,7 @@ package android.view {
}
@FlaggedApi("com.android.window.flags.sdk_desired_present_time") public static final class SurfaceControl.TransactionStats {
- method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") public long getLatchTime();
+ method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") public long getLatchTimeNanos();
method @FlaggedApi("com.android.window.flags.sdk_desired_present_time") @NonNull public android.hardware.SyncFence getPresentFence();
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index d26662d9039d..2c01e3f42631 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -12,6 +12,7 @@ package android {
field @Deprecated public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO";
field public static final String ACCESS_FPS_COUNTER = "android.permission.ACCESS_FPS_COUNTER";
field public static final String ACCESS_INSTANT_APPS = "android.permission.ACCESS_INSTANT_APPS";
+ field @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public static final String ACCESS_LAST_KNOWN_CELL_ID = "android.permission.ACCESS_LAST_KNOWN_CELL_ID";
field public static final String ACCESS_LOCUS_ID_USAGE_STATS = "android.permission.ACCESS_LOCUS_ID_USAGE_STATS";
field public static final String ACCESS_MOCK_LOCATION = "android.permission.ACCESS_MOCK_LOCATION";
field public static final String ACCESS_MTP = "android.permission.ACCESS_MTP";
@@ -133,6 +134,7 @@ package android {
field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
field public static final String GET_APP_METADATA = "android.permission.GET_APP_METADATA";
field public static final String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS";
+ field @FlaggedApi("android.app.bic_client") public static final String GET_BACKGROUND_INSTALLED_PACKAGES = "android.permission.GET_BACKGROUND_INSTALLED_PACKAGES";
field @FlaggedApi("android.app.get_binding_uid_importance") public static final String GET_BINDING_UID_IMPORTANCE = "android.permission.GET_BINDING_UID_IMPORTANCE";
field public static final String GET_HISTORICAL_APP_OPS_STATS = "android.permission.GET_HISTORICAL_APP_OPS_STATS";
field public static final String GET_PROCESS_STATE_AND_OOM_SCORE = "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE";
@@ -294,6 +296,7 @@ package android {
field public static final String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES";
field public static final String READ_SAFETY_CENTER_STATUS = "android.permission.READ_SAFETY_CENTER_STATUS";
field public static final String READ_SEARCH_INDEXABLES = "android.permission.READ_SEARCH_INDEXABLES";
+ field @FlaggedApi("android.app.system_terms_of_address_enabled") public static final String READ_SYSTEM_GRAMMATICAL_GENDER = "android.permission.READ_SYSTEM_GRAMMATICAL_GENDER";
field public static final String READ_SYSTEM_UPDATE_INFO = "android.permission.READ_SYSTEM_UPDATE_INFO";
field public static final String READ_WALLPAPER_INTERNAL = "android.permission.READ_WALLPAPER_INTERNAL";
field public static final String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL";
@@ -419,6 +422,7 @@ package android {
public static final class R.attr {
field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600
+ field @FlaggedApi("android.content.res.manifest_flagging") public static final int featureFlag;
field public static final int gameSessionService = 16844373; // 0x1010655
field public static final int hotwordDetectionService = 16844326; // 0x1010626
field @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") public static final int isVirtualDeviceOnly;
@@ -552,6 +556,7 @@ package android.app {
public class ActivityManager {
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void addOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener, int);
+ method @FlaggedApi("android.app.uid_importance_listener_for_uids") @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void addOnUidImportanceListener(@NonNull android.app.ActivityManager.OnUidImportanceListener, int, @Nullable int[]);
method @RequiresPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) public void forceStopPackage(String);
method @FlaggedApi("android.app.get_binding_uid_importance") @RequiresPermission(android.Manifest.permission.GET_BINDING_UID_IMPORTANCE) public int getBindingUidImportance(int);
method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.INTERACT_ACROSS_USERS_FULL"}) public static int getCurrentUser();
@@ -622,6 +627,7 @@ package android.app {
field public static final String OPSTR_ACCEPT_HANDOVER = "android:accept_handover";
field public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility";
field public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
+ field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String OPSTR_ACCESS_RESTRICTED_SETTINGS = "android:access_restricted_settings";
field public static final String OPSTR_ACTIVATE_PLATFORM_VPN = "android:activate_platform_vpn";
field public static final String OPSTR_ACTIVATE_VPN = "android:activate_vpn";
field public static final String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot";
@@ -1614,6 +1620,7 @@ package android.app.ambientcontext {
field public static final int LEVEL_MEDIUM_HIGH = 4; // 0x4
field public static final int LEVEL_MEDIUM_LOW = 2; // 0x2
field public static final int LEVEL_UNKNOWN = 0; // 0x0
+ field @FlaggedApi("android.app.ambient_heart_rate") public static final int RATE_PER_MINUTE_UNKNOWN = -1; // 0xffffffff
}
public static final class AmbientContextEvent.Builder {
@@ -3541,6 +3548,7 @@ package android.content {
field public static final String CLOUDSEARCH_SERVICE = "cloudsearch";
field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
field public static final String CONTEXTHUB_SERVICE = "contexthub";
+ field @FlaggedApi("android.permission.flags.enhanced_confirmation_mode_apis_enabled") public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation";
field public static final String ETHERNET_SERVICE = "ethernet";
field public static final String EUICC_CARD_SERVICE = "euicc_card";
field public static final String FONT_SERVICE = "font";
@@ -4219,11 +4227,14 @@ package android.content.pm {
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserProperties> CREATOR;
field public static final int CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT = 1; // 0x1
field public static final int CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION = 0; // 0x0
+ field public static final int CROSS_PROFILE_CONTENT_SHARING_UNKNOWN = -1; // 0xffffffff
field public static final int SHOW_IN_QUIET_MODE_DEFAULT = 2; // 0x2
field public static final int SHOW_IN_QUIET_MODE_HIDDEN = 1; // 0x1
field public static final int SHOW_IN_QUIET_MODE_PAUSED = 0; // 0x0
+ field public static final int SHOW_IN_QUIET_MODE_UNKNOWN = -1; // 0xffffffff
field public static final int SHOW_IN_SHARING_SURFACES_NO = 2; // 0x2
field public static final int SHOW_IN_SHARING_SURFACES_SEPARATE = 1; // 0x1
+ field public static final int SHOW_IN_SHARING_SURFACES_UNKNOWN = -1; // 0xffffffff
field public static final int SHOW_IN_SHARING_SURFACES_WITH_PARENT = 0; // 0x0
}
@@ -9978,6 +9989,7 @@ package android.nfc.cardemulation {
@FlaggedApi("android.nfc.enable_nfc_mainline") public final class ApduServiceInfo implements android.os.Parcelable {
ctor @FlaggedApi("android.nfc.enable_nfc_mainline") public ApduServiceInfo(@NonNull android.content.pm.PackageManager, @NonNull android.content.pm.ResolveInfo, boolean) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void addPollingLoopFilter(@NonNull String);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public int describeContents();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dump(@NonNull android.os.ParcelFileDescriptor, @NonNull java.io.PrintWriter, @NonNull String[]);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void dumpDebug(@NonNull android.util.proto.ProtoOutputStream);
@@ -9988,6 +10000,7 @@ package android.nfc.cardemulation {
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getDescription();
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.nfc.cardemulation.AidGroup getDynamicAidGroupForCategory(@NonNull String);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @Nullable public String getOffHostSecureElement();
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") @NonNull public java.util.List<java.lang.String> getPollingLoopFilters();
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getPrefixAids();
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public String getSettingsActivityName();
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<java.lang.String> getSubsetAids();
@@ -10000,6 +10013,7 @@ package android.nfc.cardemulation {
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public android.graphics.drawable.Drawable loadIcon(@NonNull android.content.pm.PackageManager);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public CharSequence loadLabel(@NonNull android.content.pm.PackageManager);
method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public boolean removeDynamicAidGroupForCategory(@NonNull String);
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") public void removePollingLoopFilter(@NonNull String);
method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresScreenOn();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public boolean requiresUnlock();
method @FlaggedApi("android.nfc.enable_nfc_mainline") public void resetOffHostSecureElement();
@@ -10038,12 +10052,17 @@ package android.os {
field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_CHARGING_POLICY = 9; // 0x9
field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_FIRST_USAGE_DATE = 8; // 0x8
field @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_MANUFACTURING_DATE = 7; // 0x7
+ field @FlaggedApi("android.os.battery_part_status_api") @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_PART_STATUS = 12; // 0xc
+ field @FlaggedApi("android.os.battery_part_status_api") @RequiresPermission(android.Manifest.permission.BATTERY_STATS) public static final int BATTERY_PROPERTY_SERIAL_NUMBER = 11; // 0xb
field public static final int CHARGING_POLICY_ADAPTIVE_AC = 3; // 0x3
field public static final int CHARGING_POLICY_ADAPTIVE_AON = 2; // 0x2
field public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE = 4; // 0x4
field public static final int CHARGING_POLICY_DEFAULT = 1; // 0x1
field public static final String EXTRA_EVENTS = "android.os.extra.EVENTS";
field public static final String EXTRA_EVENT_TIMESTAMP = "android.os.extra.EVENT_TIMESTAMP";
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_ORIGINAL = 1; // 0x1
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_REPLACED = 2; // 0x2
+ field @FlaggedApi("android.os.battery_part_status_api") public static final int PART_STATUS_UNSUPPORTED = 0; // 0x0
}
public final class BatterySaverPolicyConfig implements android.os.Parcelable {
@@ -14554,6 +14573,7 @@ package android.telephony {
field public static final int EVENT_SERVICE_STATE_CHANGED = 1; // 0x1
field public static final int EVENT_SIGNAL_STRENGTHS_CHANGED = 9; // 0x9
field public static final int EVENT_SIGNAL_STRENGTH_CHANGED = 2; // 0x2
+ field @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED = 41; // 0x29
field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_SRVCC_STATE_CHANGED = 16; // 0x10
field public static final int EVENT_USER_MOBILE_DATA_STATE_CHANGED = 20; // 0x14
field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_VOICE_ACTIVATION_STATE_CHANGED = 18; // 0x12
@@ -14600,6 +14620,10 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onRadioPowerStateChanged(int);
}
+ @FlaggedApi("com.android.internal.telephony.flags.simultaneous_calling_indications") public static interface TelephonyCallback.SimultaneousCellularCallingSupportListener {
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onSimultaneousCellularCallingSubscriptionsChanged(@NonNull java.util.Set<java.lang.Integer>);
+ }
+
public static interface TelephonyCallback.SrvccStateListener {
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void onSrvccStateChanged(int);
}
@@ -14675,6 +14699,7 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEmergencyNumberDbVersion();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @Nullable @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, "com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"}) public android.telephony.CellIdentity getLastKnownCellIdentity();
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping();
method public int getMaxNumberOfSimultaneouslyActiveSims();
method public static long getMaxNumberVerificationTimeoutMillis();
@@ -14778,6 +14803,7 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setSystemSelectionChannels(@NonNull java.util.List<android.telephony.RadioAccessSpecifier>);
method @Deprecated public void setVisualVoicemailEnabled(android.telecom.PhoneAccountHandle, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setVoiceActivationState(int);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE) public void setVoiceServiceStateOverride(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void shutdownAllRadios();
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyIccLockPin(@NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.PinResult supplyIccLockPuk(@NonNull String, @NonNull String);
@@ -14917,6 +14943,11 @@ package android.telephony {
field public static final int ERROR_UNKNOWN = 0; // 0x0
}
+ @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") public class TelephonyRegistryManager {
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyCallStateChangedForAllSubscriptions(int, @Nullable String);
+ method public void notifyOutgoingEmergencyCall(int, int, @NonNull android.telephony.emergency.EmergencyNumber);
+ }
+
public final class ThermalMitigationRequest implements android.os.Parcelable {
method public int describeContents();
method @Nullable public android.telephony.DataThrottlingRequest getDataThrottlingRequest();
@@ -17154,6 +17185,7 @@ package android.telephony.satellite {
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_NR_NTN = 2; // 0x2
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_PROPRIETARY = 4; // 0x4
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NT_RADIO_TECHNOLOGY_UNKNOWN = 0; // 0x0
+ field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT = 2; // 0x2
field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1; // 0x1
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_IDLE = 0; // 0x0
field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int SATELLITE_DATAGRAM_TRANSFER_STATE_RECEIVE_FAILED = 7; // 0x7
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index bbe03a3d11a2..a840fc2b394f 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -284,16 +284,6 @@ package android.app {
method public default void onOpActiveChanged(@NonNull String, int, @NonNull String, @Nullable String, boolean, int, int);
}
- public final class AutomaticZenRule implements android.os.Parcelable {
- method @FlaggedApi("android.app.modes_api") public int getUserModifiedFields();
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_INTERRUPTION_FILTER = 2; // 0x2
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_NAME = 1; // 0x1
- }
-
- @FlaggedApi("android.app.modes_api") public static final class AutomaticZenRule.Builder {
- method @FlaggedApi("android.app.modes_api") @NonNull public android.app.AutomaticZenRule.Builder setUserModifiedFields(int);
- }
-
public class BroadcastOptions extends android.app.ComponentOptions {
ctor public BroadcastOptions();
ctor public BroadcastOptions(@NonNull android.os.Bundle);
@@ -493,10 +483,15 @@ package android.app {
}
public class UiModeManager {
+ method @FlaggedApi("android.app.modes_api") @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE) public int getAttentionModeThemeOverlay();
method public boolean isNightModeLocked();
method public boolean isUiModeLocked();
method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean releaseProjection(int);
method @RequiresPermission(value=android.Manifest.permission.TOGGLE_AUTOMOTIVE_PROJECTION, conditional=true) public boolean requestProjection(int);
+ field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_DAY = 1002; // 0x3ea
+ field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_NIGHT = 1001; // 0x3e9
+ field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_OFF = 1000; // 0x3e8
+ field @FlaggedApi("android.app.modes_api") public static final int MODE_ATTENTION_THEME_OVERLAY_UNKNOWN = -1; // 0xffffffff
field public static final int PROJECTION_TYPE_ALL = -1; // 0xffffffff
field public static final int PROJECTION_TYPE_AUTOMOTIVE = 1; // 0x1
field public static final int PROJECTION_TYPE_NONE = 0; // 0x0
@@ -1177,6 +1172,7 @@ package android.content.pm {
method public int getShowInLauncher();
field public static final int SHOW_IN_LAUNCHER_NO = 2; // 0x2
field public static final int SHOW_IN_LAUNCHER_SEPARATE = 1; // 0x1
+ field public static final int SHOW_IN_LAUNCHER_UNKNOWN = -1; // 0xffffffff
field public static final int SHOW_IN_LAUNCHER_WITH_PARENT = 0; // 0x0
}
@@ -3021,47 +3017,8 @@ package android.service.notification {
method @Deprecated public boolean isBound();
}
- @FlaggedApi("android.app.modes_api") public final class ZenDeviceEffects implements android.os.Parcelable {
- method public int getUserModifiedFields();
- field public static final int FIELD_DIM_WALLPAPER = 4; // 0x4
- field public static final int FIELD_DISABLE_AUTO_BRIGHTNESS = 16; // 0x10
- field public static final int FIELD_DISABLE_TAP_TO_WAKE = 32; // 0x20
- field public static final int FIELD_DISABLE_TILT_TO_WAKE = 64; // 0x40
- field public static final int FIELD_DISABLE_TOUCH = 128; // 0x80
- field public static final int FIELD_GRAYSCALE = 1; // 0x1
- field public static final int FIELD_MAXIMIZE_DOZE = 512; // 0x200
- field public static final int FIELD_MINIMIZE_RADIO_USAGE = 256; // 0x100
- field public static final int FIELD_NIGHT_MODE = 8; // 0x8
- field public static final int FIELD_SUPPRESS_AMBIENT_DISPLAY = 2; // 0x2
- }
-
- @FlaggedApi("android.app.modes_api") public static final class ZenDeviceEffects.Builder {
- method @NonNull public android.service.notification.ZenDeviceEffects.Builder setUserModifiedFields(int);
- }
-
- public final class ZenPolicy implements android.os.Parcelable {
- method @FlaggedApi("android.app.modes_api") public int getUserModifiedFields();
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_ALLOW_CHANNELS = 8; // 0x8
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_CALLS = 2; // 0x2
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_CONVERSATIONS = 4; // 0x4
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_MESSAGES = 1; // 0x1
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_ALARMS = 128; // 0x80
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_EVENTS = 32; // 0x20
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_MEDIA = 256; // 0x100
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS = 64; // 0x40
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_PRIORITY_CATEGORY_SYSTEM = 512; // 0x200
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_AMBIENT = 32768; // 0x8000
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_BADGE = 16384; // 0x4000
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT = 1024; // 0x400
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_LIGHTS = 2048; // 0x800
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_NOTIFICATION_LIST = 65536; // 0x10000
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_PEEK = 4096; // 0x1000
- field @FlaggedApi("android.app.modes_api") public static final int FIELD_VISUAL_EFFECT_STATUS_BAR = 8192; // 0x2000
- }
-
public static final class ZenPolicy.Builder {
ctor public ZenPolicy.Builder(@Nullable android.service.notification.ZenPolicy);
- method @FlaggedApi("android.app.modes_api") @NonNull public android.service.notification.ZenPolicy.Builder setUserModifiedFields(int);
}
}
@@ -3292,7 +3249,7 @@ package android.telephony {
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
method @Deprecated public void setCarrierTestOverride(String, String, String, String, String, String, String);
method public void setCarrierTestOverride(String, String, String, String, String, String, String, String, String);
- method @RequiresPermission(android.Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE) public void setVoiceServiceStateOverride(boolean);
+ method @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies") @RequiresPermission(android.Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE) public void setVoiceServiceStateOverride(boolean);
field public static final int HAL_SERVICE_DATA = 1; // 0x1
field public static final int HAL_SERVICE_IMS = 7; // 0x7
field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2
diff --git a/core/java/android/adaptiveauth/OWNERS b/core/java/android/adaptiveauth/OWNERS
new file mode 100644
index 000000000000..0218a7835586
--- /dev/null
+++ b/core/java/android/adaptiveauth/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/adaptiveauth/OWNERS \ No newline at end of file
diff --git a/core/java/android/adaptiveauth/flags.aconfig b/core/java/android/adaptiveauth/flags.aconfig
new file mode 100644
index 000000000000..39e46bbdfa6a
--- /dev/null
+++ b/core/java/android/adaptiveauth/flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.adaptiveauth"
+
+flag {
+ name: "report_biometric_auth_attempts"
+ namespace: "biometrics"
+ description: "Control the usage of the biometric auth signal in adaptive auth"
+ bug: "285053096"
+} \ No newline at end of file
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index ebb5ba0e7b0f..5318bb722b5f 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3574,7 +3574,7 @@ public class ActivityManager {
* foreground. This may be running a window that is behind the current
* foreground (so paused and with its state saved, not interacting with
* the user, but visible to them to some degree); it may also be running
- * other services under the system's control that it inconsiders important.
+ * other services under the system's control that it considers important.
*/
public static final int IMPORTANCE_VISIBLE = 200;
@@ -3646,9 +3646,9 @@ public class ActivityManager {
public static final int IMPORTANCE_CANT_SAVE_STATE = 350;
/**
- * Constant for {@link #importance}: This process process contains
- * cached code that is expendable, not actively running any app components
- * we care about.
+ * Constant for {@link #importance}: This process contains cached code
+ * that is expendable, not actively running any app components we care
+ * about.
*/
public static final int IMPORTANCE_CACHED = 400;
@@ -4402,7 +4402,7 @@ public class ActivityManager {
}
/**
- * Start monitoring changes to the importance of uids running in the system.
+ * Start monitoring changes to the importance of all uids running in the system.
* @param listener The listener callback that will receive change reports.
* @param importanceCutpoint The level of importance in which the caller is interested
* in differences. For example, if {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}
@@ -4422,17 +4422,48 @@ public class ActivityManager {
@RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
public void addOnUidImportanceListener(OnUidImportanceListener listener,
@RunningAppProcessInfo.Importance int importanceCutpoint) {
- synchronized (this) {
+ addOnUidImportanceListenerInternal(listener, importanceCutpoint, null /* uids */);
+ }
+
+ /**
+ * Start monitoring changes to the importance of given uids running in the system.
+ *
+ * @param listener The listener callback that will receive change reports.
+ * @param importanceCutpoint The level of importance in which the caller is interested
+ * in differences. For example, if {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}
+ * is used here, you will receive a call each time a uids importance transitions between
+ * being <= {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE} and
+ * > {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}.
+ * @param uids The UIDs that this listener is interested with. A {@code null} value means
+ * all UIDs will be monitored by this listener, this will be equivalent to the
+ * {@link #addOnUidImportanceListener(OnUidImportanceListener, int)} in this case.
+ *
+ * @throws IllegalArgumentException If the listener is already registered.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_UID_IMPORTANCE_LISTENER_FOR_UIDS)
+ @SystemApi
+ @SuppressLint("SamShouldBeLast")
+ @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+ public void addOnUidImportanceListener(@NonNull OnUidImportanceListener listener,
+ @RunningAppProcessInfo.Importance int importanceCutpoint, @Nullable int[] uids) {
+ addOnUidImportanceListenerInternal(listener, importanceCutpoint, uids);
+ }
+
+ @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+ private void addOnUidImportanceListenerInternal(@NonNull OnUidImportanceListener listener,
+ @RunningAppProcessInfo.Importance int importanceCutpoint, @Nullable int[] uids) {
+ synchronized (mImportanceListeners) {
if (mImportanceListeners.containsKey(listener)) {
throw new IllegalArgumentException("Listener already registered: " + listener);
}
// TODO: implement the cut point in the system process to avoid IPCs.
MyUidObserver observer = new MyUidObserver(listener, mContext);
try {
- getService().registerUidObserver(observer,
+ getService().registerUidObserverForUids(observer,
UID_OBSERVER_PROCSTATE | UID_OBSERVER_GONE,
RunningAppProcessInfo.importanceToProcState(importanceCutpoint),
- mContext.getOpPackageName());
+ mContext.getOpPackageName(), uids);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -4450,7 +4481,7 @@ public class ActivityManager {
@SystemApi
@RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
public void removeOnUidImportanceListener(OnUidImportanceListener listener) {
- synchronized (this) {
+ synchronized (mImportanceListeners) {
MyUidObserver observer = mImportanceListeners.remove(listener);
if (observer == null) {
throw new IllegalArgumentException("Listener not registered: " + listener);
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 1db1caf51800..4b2e93fda171 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1540,9 +1540,16 @@ public class AppOpsManager {
public static final int OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER =
AppProtoEnums.APP_OP_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER;
+ /**
+ * See {@link #OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER}.
+ * @hide
+ */
+ public static final int OP_READ_SYSTEM_GRAMMATICAL_GENDER =
+ AppProtoEnums.APP_OP_READ_SYSTEM_GRAMMATICAL_GENDER;
+
/** @hide */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- public static final int _NUM_OP = 143;
+ public static final int _NUM_OP = 144;
/**
* All app ops represented as strings.
@@ -2180,6 +2187,8 @@ public class AppOpsManager {
*
* @hide
*/
+ @FlaggedApi(android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @SystemApi
public static final String OPSTR_ACCESS_RESTRICTED_SETTINGS =
"android:access_restricted_settings";
@@ -2373,6 +2382,14 @@ public class AppOpsManager {
public static final String OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER =
"android:rapid_clear_notifications_by_listener";
+ /**
+ * Allows an application to read the system grammatical gender.
+ *
+ * @hide
+ */
+ public static final String OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER =
+ "android:read_system_grammatical_gender";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
/** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -2484,6 +2501,7 @@ public class AppOpsManager {
OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA,
OP_MEDIA_ROUTING_CONTROL,
+ OP_READ_SYSTEM_GRAMMATICAL_GENDER,
};
static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2936,6 +2954,10 @@ public class AppOpsManager {
OPSTR_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER,
"RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER")
.setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+ new AppOpInfo.Builder(OP_READ_SYSTEM_GRAMMATICAL_GENDER,
+ OPSTR_READ_SYSTEM_GRAMMATICAL_GENDER, "READ_SYSTEM_GRAMMATICAL_GENDER")
+ .setPermission(Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER)
+ .build(),
};
// The number of longs needed to form a full bitmask of app ops
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index cc3eac1bbf01..afa513dbaaef 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -26,11 +26,19 @@ import android.icu.text.SimpleDateFormat;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
+import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.Xml;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import android.util.proto.WireTypeMismatchException;
+import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParserException;
+
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -40,6 +48,7 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Map;
+import java.util.Objects;
/**
* Describes information related to an application process's startup.
@@ -215,6 +224,11 @@ public final class ApplicationStartInfo implements Parcelable {
private int mDefiningUid;
/**
+ * @see #getPackageName
+ */
+ private String mPackageName;
+
+ /**
* @see #getProcessName
*/
private String mProcessName;
@@ -349,6 +363,14 @@ public final class ApplicationStartInfo implements Parcelable {
}
/**
+ * @see #getPackageName
+ * @hide
+ */
+ public void setPackageName(final String packageName) {
+ mPackageName = intern(packageName);
+ }
+
+ /**
* @see #getProcessName
* @hide
*/
@@ -461,6 +483,15 @@ public final class ApplicationStartInfo implements Parcelable {
}
/**
+ * Name of first package running in this process;
+ *
+ * @hide
+ */
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
* The actual process name it was running with.
*
* <p class="note"> Note: field will be set for any {@link #getStartupState} value.</p>
@@ -555,6 +586,7 @@ public final class ApplicationStartInfo implements Parcelable {
dest.writeInt(mRealUid);
dest.writeInt(mPackageUid);
dest.writeInt(mDefiningUid);
+ dest.writeString(mPackageName);
dest.writeString(mProcessName);
dest.writeInt(mReason);
dest.writeInt(mStartupTimestampsNs == null ? 0 : mStartupTimestampsNs.size());
@@ -579,6 +611,7 @@ public final class ApplicationStartInfo implements Parcelable {
mRealUid = other.mRealUid;
mPackageUid = other.mPackageUid;
mDefiningUid = other.mDefiningUid;
+ mPackageName = other.mPackageName;
mProcessName = other.mProcessName;
mReason = other.mReason;
mStartupTimestampsNs = other.mStartupTimestampsNs;
@@ -593,6 +626,7 @@ public final class ApplicationStartInfo implements Parcelable {
mRealUid = in.readInt();
mPackageUid = in.readInt();
mDefiningUid = in.readInt();
+ mPackageName = intern(in.readString());
mProcessName = intern(in.readString());
mReason = in.readInt();
int starupTimestampCount = in.readInt();
@@ -624,6 +658,11 @@ public final class ApplicationStartInfo implements Parcelable {
}
};
+ private static final String PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS = "timestamps";
+ private static final String PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP = "timestamp";
+ private static final String PROTO_SERIALIZER_ATTRIBUTE_KEY = "key";
+ private static final String PROTO_SERIALIZER_ATTRIBUTE_TS = "ts";
+
/**
* Write to a protocol buffer output stream. Protocol buffer message definition at {@link
* android.app.ApplicationStartInfoProto}
@@ -644,9 +683,22 @@ public final class ApplicationStartInfo implements Parcelable {
if (mStartupTimestampsNs != null && mStartupTimestampsNs.size() > 0) {
ByteArrayOutputStream timestampsBytes = new ByteArrayOutputStream();
ObjectOutputStream timestampsOut = new ObjectOutputStream(timestampsBytes);
- timestampsOut.writeObject(mStartupTimestampsNs);
+ TypedXmlSerializer serializer = Xml.resolveSerializer(timestampsOut);
+ serializer.startDocument(null, true);
+ serializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS);
+ for (int i = 0; i < mStartupTimestampsNs.size(); i++) {
+ serializer.startTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP);
+ serializer.attributeInt(null, PROTO_SERIALIZER_ATTRIBUTE_KEY,
+ mStartupTimestampsNs.keyAt(i));
+ serializer.attributeLong(null, PROTO_SERIALIZER_ATTRIBUTE_TS,
+ mStartupTimestampsNs.valueAt(i));
+ serializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP);
+ }
+ serializer.endTag(null, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS);
+ serializer.endDocument();
proto.write(ApplicationStartInfoProto.STARTUP_TIMESTAMPS,
timestampsBytes.toByteArray());
+ timestampsOut.close();
}
proto.write(ApplicationStartInfoProto.START_TYPE, mStartType);
if (mStartIntent != null) {
@@ -697,7 +749,24 @@ public final class ApplicationStartInfo implements Parcelable {
ByteArrayInputStream timestampsBytes = new ByteArrayInputStream(proto.readBytes(
ApplicationStartInfoProto.STARTUP_TIMESTAMPS));
ObjectInputStream timestampsIn = new ObjectInputStream(timestampsBytes);
- mStartupTimestampsNs = (ArrayMap<Integer, Long>) timestampsIn.readObject();
+ mStartupTimestampsNs = new ArrayMap<Integer, Long>();
+ try {
+ TypedXmlPullParser parser = Xml.resolvePullParser(timestampsIn);
+ XmlUtils.beginDocument(parser, PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMPS);
+ int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ if (PROTO_SERIALIZER_ATTRIBUTE_TIMESTAMP.equals(parser.getName())) {
+ int key = parser.getAttributeInt(null,
+ PROTO_SERIALIZER_ATTRIBUTE_KEY);
+ long ts = parser.getAttributeLong(null,
+ PROTO_SERIALIZER_ATTRIBUTE_TS);
+ mStartupTimestampsNs.put(key, ts);
+ }
+ }
+ } catch (XmlPullParserException e) {
+ // Timestamps lost
+ }
+ timestampsIn.close();
break;
case (int) ApplicationStartInfoProto.START_TYPE:
mStartType = proto.readInt(ApplicationStartInfoProto.START_TYPE);
@@ -734,6 +803,7 @@ public final class ApplicationStartInfo implements Parcelable {
.append(" definingUid=").append(mDefiningUid)
.append(" user=").append(UserHandle.getUserId(mPackageUid))
.append('\n')
+ .append(" package=").append(mPackageName)
.append(" process=").append(mProcessName)
.append(" startupState=").append(mStartupState)
.append(" reason=").append(reasonToString(mReason))
@@ -782,4 +852,35 @@ public final class ApplicationStartInfo implements Parcelable {
default -> "";
};
}
+
+ /** @hide */
+ @Override
+ public boolean equals(@Nullable Object other) {
+ if (other == null || !(other instanceof ApplicationStartInfo)) {
+ return false;
+ }
+ final ApplicationStartInfo o = (ApplicationStartInfo) other;
+ return mPid == o.mPid && mRealUid == o.mRealUid && mPackageUid == o.mPackageUid
+ && mDefiningUid == o.mDefiningUid && mReason == o.mReason
+ && mStartupState == o.mStartupState && mStartType == o.mStartType
+ && mLaunchMode == o.mLaunchMode && TextUtils.equals(mProcessName, o.mProcessName)
+ && timestampsEquals(o);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPid, mRealUid, mPackageUid, mDefiningUid, mReason, mStartupState,
+ mStartType, mLaunchMode, mProcessName,
+ mStartupTimestampsNs);
+ }
+
+ private boolean timestampsEquals(@NonNull ApplicationStartInfo other) {
+ if (mStartupTimestampsNs == null && other.mStartupTimestampsNs == null) {
+ return true;
+ }
+ if (mStartupTimestampsNs == null || other.mStartupTimestampsNs == null) {
+ return false;
+ }
+ return mStartupTimestampsNs.equals(other.mStartupTimestampsNs);
+ }
}
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 5b354fc3b9ed..d57a4e583a1a 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -23,7 +23,6 @@ import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.TestApi;
import android.app.NotificationManager.InterruptionFilter;
import android.content.ComponentName;
import android.net.Uri;
@@ -113,8 +112,8 @@ public final class AutomaticZenRule implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
public @interface Type {}
- /** Used to track which rule variables have been modified by the user.
- * Should be checked against the bitmask {@link #getUserModifiedFields()}.
+ /**
+ * Enum for the user-modifiable fields in this object.
* @hide
*/
@IntDef(flag = true, prefix = { "FIELD_" }, value = {
@@ -128,13 +127,11 @@ public final class AutomaticZenRule implements Parcelable {
* @hide
*/
@FlaggedApi(Flags.FLAG_MODES_API)
- @TestApi
public static final int FIELD_NAME = 1 << 0;
/**
* @hide
*/
@FlaggedApi(Flags.FLAG_MODES_API)
- @TestApi
public static final int FIELD_INTERRUPTION_FILTER = 1 << 1;
private boolean enabled;
@@ -153,7 +150,6 @@ public final class AutomaticZenRule implements Parcelable {
private int mIconResId;
private String mTriggerDescription;
private boolean mAllowManualInvocation;
- private @ModifiableField int mUserModifiedFields; // Bitwise representation
/**
* The maximum string length for any string contained in this automatic zen rule. This pertains
@@ -256,7 +252,6 @@ public final class AutomaticZenRule implements Parcelable {
mIconResId = source.readInt();
mTriggerDescription = getTrimmedString(source.readString(), MAX_DESC_LENGTH);
mType = source.readInt();
- mUserModifiedFields = source.readInt();
}
}
@@ -307,8 +302,7 @@ public final class AutomaticZenRule implements Parcelable {
* Returns whether this rule's name has been modified by the user.
* @hide
*/
- // TODO: b/310620812 - Replace with mUserModifiedFields & FIELD_NAME once
- // FLAG_MODES_API is inlined.
+ // TODO: b/310620812 - Consider removing completely. Seems not be used anywhere except tests.
public boolean isModified() {
return mModified;
}
@@ -506,32 +500,6 @@ public final class AutomaticZenRule implements Parcelable {
return type;
}
- /**
- * Gets the bitmask representing which fields are user modified. Bits are set using
- * {@link ModifiableField}.
- * @hide
- */
- @FlaggedApi(Flags.FLAG_MODES_API)
- @TestApi
- public @ModifiableField int getUserModifiedFields() {
- return mUserModifiedFields;
- }
-
- /**
- * Returns {@code true} if the {@link AutomaticZenRule} can be updated.
- * When this returns {@code false}, calls to
- * {@link NotificationManager#updateAutomaticZenRule(String, AutomaticZenRule)}) with this rule
- * will ignore changes to user-configurable fields.
- */
- @FlaggedApi(Flags.FLAG_MODES_API)
- public boolean canUpdate() {
- // The rule is considered updateable if its bitmask has no user modifications, and
- // the bitmasks of the policy and device effects have no modification.
- return mUserModifiedFields == 0
- && (mZenPolicy == null || mZenPolicy.getUserModifiedFields() == 0)
- && (mDeviceEffects == null || mDeviceEffects.getUserModifiedFields() == 0);
- }
-
@Override
public int describeContents() {
return 0;
@@ -560,7 +528,6 @@ public final class AutomaticZenRule implements Parcelable {
dest.writeInt(mIconResId);
dest.writeString(mTriggerDescription);
dest.writeInt(mType);
- dest.writeInt(mUserModifiedFields);
}
}
@@ -582,16 +549,14 @@ public final class AutomaticZenRule implements Parcelable {
.append(",allowManualInvocation=").append(mAllowManualInvocation)
.append(",iconResId=").append(mIconResId)
.append(",triggerDescription=").append(mTriggerDescription)
- .append(",type=").append(mType)
- .append(",userModifiedFields=")
- .append(modifiedFieldsToString(mUserModifiedFields));
+ .append(",type=").append(mType);
}
return sb.append(']').toString();
}
- @FlaggedApi(Flags.FLAG_MODES_API)
- private String modifiedFieldsToString(int bitmask) {
+ /** @hide */
+ public static String fieldsToString(@ModifiableField int bitmask) {
ArrayList<String> modified = new ArrayList<>();
if ((bitmask & FIELD_NAME) != 0) {
modified.add("FIELD_NAME");
@@ -623,8 +588,7 @@ public final class AutomaticZenRule implements Parcelable {
&& other.mAllowManualInvocation == mAllowManualInvocation
&& other.mIconResId == mIconResId
&& Objects.equals(other.mTriggerDescription, mTriggerDescription)
- && other.mType == mType
- && other.mUserModifiedFields == mUserModifiedFields;
+ && other.mType == mType;
}
return finalEquals;
}
@@ -634,8 +598,7 @@ public final class AutomaticZenRule implements Parcelable {
if (Flags.modesApi()) {
return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
configurationActivity, mZenPolicy, mDeviceEffects, mModified, creationTime,
- mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType,
- mUserModifiedFields);
+ mPkg, mAllowManualInvocation, mIconResId, mTriggerDescription, mType);
}
return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
configurationActivity, mZenPolicy, mModified, creationTime, mPkg);
@@ -704,7 +667,6 @@ public final class AutomaticZenRule implements Parcelable {
private boolean mAllowManualInvocation;
private long mCreationTime;
private String mPkg;
- private @ModifiableField int mUserModifiedFields;
public Builder(@NonNull AutomaticZenRule rule) {
mName = rule.getName();
@@ -721,7 +683,6 @@ public final class AutomaticZenRule implements Parcelable {
mAllowManualInvocation = rule.isManualInvocationAllowed();
mCreationTime = rule.getCreationTime();
mPkg = rule.getPackageName();
- mUserModifiedFields = rule.mUserModifiedFields;
}
public Builder(@NonNull String name, @NonNull Uri conditionId) {
@@ -848,19 +809,6 @@ public final class AutomaticZenRule implements Parcelable {
return this;
}
- /**
- * Sets the bitmask representing which fields have been user-modified.
- * This method should not be used outside of tests. The value of userModifiedFields
- * should be set based on what values are changed when a rule is populated or updated..
- * @hide
- */
- @FlaggedApi(Flags.FLAG_MODES_API)
- @TestApi
- public @NonNull Builder setUserModifiedFields(@ModifiableField int userModifiedFields) {
- mUserModifiedFields = userModifiedFields;
- return this;
- }
-
public @NonNull AutomaticZenRule build() {
AutomaticZenRule rule = new AutomaticZenRule(mName, mOwner, mConfigurationActivity,
mConditionId, mPolicy, mInterruptionFilter, mEnabled);
@@ -871,7 +819,6 @@ public final class AutomaticZenRule implements Parcelable {
rule.mIconResId = mIconResId;
rule.mAllowManualInvocation = mAllowManualInvocation;
rule.setPackageName(mPkg);
- rule.mUserModifiedFields = mUserModifiedFields;
return rule;
}
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
index a55121aaa12c..483a6e11a42a 100644
--- a/core/java/android/app/GrammaticalInflectionManager.java
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -114,7 +114,7 @@ public class GrammaticalInflectionManager {
}
try {
- mService.setSystemWideGrammaticalGender(mContext.getUserId(), grammaticalGender);
+ mService.setSystemWideGrammaticalGender(grammaticalGender, mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -131,7 +131,8 @@ public class GrammaticalInflectionManager {
@Configuration.GrammaticalGender
public int getSystemGrammaticalGender() {
try {
- return mService.getSystemGrammaticalGender(mContext.getUserId());
+ return mService.getSystemGrammaticalGender(mContext.getAttributionSource(),
+ mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index b5d88e878d8d..47403d26c907 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -116,6 +116,7 @@ interface IActivityManager {
* @throws RemoteException
* @return Returns A binder token identifying the UidObserver registration.
*/
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)")
IBinder registerUidObserverForUids(in IUidObserver observer, int which, int cutpoint,
String callingPackage, in int[] uids);
diff --git a/core/java/android/app/IGrammaticalInflectionManager.aidl b/core/java/android/app/IGrammaticalInflectionManager.aidl
index 48a48416d592..86f2e9110889 100644
--- a/core/java/android/app/IGrammaticalInflectionManager.aidl
+++ b/core/java/android/app/IGrammaticalInflectionManager.aidl
@@ -1,5 +1,22 @@
+/**
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package android.app;
+import android.content.AttributionSource;
/**
* Internal interface used to control app-specific gender.
@@ -20,10 +37,10 @@ package android.app;
/**
* Sets the grammatical gender to system.
*/
- void setSystemWideGrammaticalGender(int userId, int gender);
+ void setSystemWideGrammaticalGender(int gender, int userId);
/**
* Gets the grammatical gender from system.
*/
- int getSystemGrammaticalGender(int userId);
+ int getSystemGrammaticalGender(in AttributionSource attributionSource, int userId);
}
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 60b34cdc1b8a..3b83024d536b 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -95,6 +95,34 @@ interface IUiModeManager {
int getNightModeCustomType();
/**
+ * Overlays current Night Mode value.
+ * {@code attentionModeThemeOverlayType}.
+ *
+ * @param attentionModeThemeOverlayType
+ * @hide
+ */
+ @EnforcePermission("MODIFY_DAY_NIGHT_MODE")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)")
+ void setAttentionModeThemeOverlay(int attentionModeThemeOverlayType);
+
+
+ /**
+ * Returns current Attention Mode overlay type.
+ * <p>
+ * returns
+ * <ul>
+ * <li>{@link #MODE_ATTENTION_OFF}</li>
+ * <li>{@link #MODE_ATTENTION_NIGHT}</li>
+ * <li>{@link #MODE_ATTENTION_DAY}</li>
+ * </ul>
+ * </p>
+ * @hide
+ */
+ @EnforcePermission("MODIFY_DAY_NIGHT_MODE")
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)")
+ int getAttentionModeThemeOverlay();
+
+ /**
* Sets the dark mode for the given application. This setting is persisted and will override the
* system configuration for this application.
* 1 - notnight mode
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 476232cb40b3..ed0cfbe3d9c3 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -5652,7 +5652,7 @@ public class Notification implements Parcelable
pillColor = Colors.flattenAlpha(
getColors(p).getTertiaryFixedDimAccentColor(), bgColor);
textColor = Colors.flattenAlpha(
- getColors(p).getOnTertiaryAccentTextColor(), pillColor);
+ getColors(p).getOnTertiaryFixedAccentTextColor(), pillColor);
}
contentView.setInt(R.id.expand_button, "setHighlightTextColor", textColor);
contentView.setInt(R.id.expand_button, "setHighlightPillColor", pillColor);
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 9cf732abb86a..d7554137fa5b 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -31,6 +31,7 @@ import android.app.appsearch.AppSearchManagerFrameworkInitializer;
import android.app.blob.BlobStoreManagerFrameworkInitializer;
import android.app.contentsuggestions.ContentSuggestionsManager;
import android.app.contentsuggestions.IContentSuggestionsManager;
+import android.app.ecm.EnhancedConfirmationFrameworkInitializer;
import android.app.job.JobSchedulerFrameworkInitializer;
import android.app.people.PeopleManager;
import android.app.prediction.AppPredictionManager;
@@ -1631,6 +1632,9 @@ public final class SystemServiceRegistry {
OnDevicePersonalizationFrameworkInitializer.registerServiceWrappers();
DeviceLockFrameworkInitializer.registerServiceWrappers();
VirtualizationFrameworkInitializer.registerServiceWrappers();
+ if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()) {
+ EnhancedConfirmationFrameworkInitializer.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/UiModeManager.java b/core/java/android/app/UiModeManager.java
index 0ccb9cddf58d..a27132872521 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -17,6 +17,7 @@
package android.app;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -265,6 +266,60 @@ public class UiModeManager {
*/
public static final int MODE_NIGHT_YES = 2;
+ /** @hide */
+ @IntDef(prefix = { "MODE_ATTENTION_THEME_OVERLAY_" }, value = {
+ MODE_ATTENTION_THEME_OVERLAY_OFF,
+ MODE_ATTENTION_THEME_OVERLAY_NIGHT,
+ MODE_ATTENTION_THEME_OVERLAY_DAY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AttentionModeThemeOverlayType {}
+
+ /** @hide */
+ @IntDef(prefix = { "MODE_ATTENTION_THEME_OVERLAY_" }, value = {
+ MODE_ATTENTION_THEME_OVERLAY_OFF,
+ MODE_ATTENTION_THEME_OVERLAY_NIGHT,
+ MODE_ATTENTION_THEME_OVERLAY_DAY,
+ MODE_ATTENTION_THEME_OVERLAY_UNKNOWN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface AttentionModeThemeOverlayReturnType {}
+
+ /**
+ * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link
+ * #getAttentionModeThemeOverlay()}: Keeps night mode as set by {@link #setNightMode(int)}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @TestApi
+ public static final int MODE_ATTENTION_THEME_OVERLAY_OFF = 1000;
+
+ /**
+ * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link
+ * #getAttentionModeThemeOverlay()}: Maintains night mode always on.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @TestApi
+ public static final int MODE_ATTENTION_THEME_OVERLAY_NIGHT = 1001;
+
+ /**
+ * Constant for {@link #setAttentionModeThemeOverlay(int)} (int)} and {@link
+ * #getAttentionModeThemeOverlay()}: Maintains night mode always off (Light).
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @TestApi
+ public static final int MODE_ATTENTION_THEME_OVERLAY_DAY = 1002;
+
+ /**
+ * Constant for {@link #getAttentionModeThemeOverlay()}: Error communication with server.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @TestApi
+ public static final int MODE_ATTENTION_THEME_OVERLAY_UNKNOWN = -1;
+
/**
* Granular types for {@link #setNightModeCustomType(int)}
* @hide
@@ -733,6 +788,55 @@ public class UiModeManager {
}
/**
+ * Overlays current Attention mode Night Mode overlay.
+ * {@code attentionModeThemeOverlayType}.
+ *
+ * @throws IllegalArgumentException if passed an unsupported type to
+ * {@code AttentionModeThemeOverlayType}.
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+ public void setAttentionModeThemeOverlay(
+ @AttentionModeThemeOverlayType int attentionModeThemeOverlayType) {
+ if (sGlobals != null) {
+ try {
+ sGlobals.mService.setAttentionModeThemeOverlay(attentionModeThemeOverlayType);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Returns the currently configured Attention Mode theme overlay.
+ * <p>
+ * May be one of:
+ * <ul>
+ * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_OFF}</li>
+ * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_NIGHT}</li>
+ * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_DAY}</li>
+ * <li>{@link #MODE_ATTENTION_THEME_OVERLAY_UNKNOWN}</li>
+ * </ul>
+ * </p>
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_MODES_API)
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+ public @AttentionModeThemeOverlayReturnType int getAttentionModeThemeOverlay() {
+ if (sGlobals != null) {
+ try {
+ return sGlobals.mService.getAttentionModeThemeOverlay();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return MODE_ATTENTION_THEME_OVERLAY_UNKNOWN;
+ }
+
+ /**
* Sets and persist the night mode for this application.
* <p>
* The mode can be one of:
diff --git a/core/java/android/app/activity_manager.aconfig b/core/java/android/app/activity_manager.aconfig
index 4fc25fd8c699..c0b299b77806 100644
--- a/core/java/android/app/activity_manager.aconfig
+++ b/core/java/android/app/activity_manager.aconfig
@@ -19,4 +19,11 @@ flag {
name: "app_restrictions_api"
description: "API to track and query restrictions applied to apps"
bug: "320150834"
-} \ No newline at end of file
+}
+
+flag {
+ namespace: "backstage_power"
+ name: "uid_importance_listener_for_uids"
+ description: "API to add OnUidImportanceListener with targetted UIDs"
+ bug: "286258140"
+}
diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java
index f94987e8495a..5ab7991c6326 100644
--- a/core/java/android/app/ambientcontext/AmbientContextEvent.java
+++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java
@@ -89,8 +89,11 @@ public final class AmbientContextEvent implements Parcelable {
*/
public static final String KEY_VENDOR_WEARABLE_EVENT_NAME = "wearable_event_name";
- /** Default value for the rate per minute data field. */
- private static final int RATE_PER_MINUTE_UNKNOWN = -1;
+ /**
+ * Default value for {@link #getRatePerMinute}. Indicates that the rate of the event is unknown.
+ */
+ @FlaggedApi(android.app.Flags.FLAG_AMBIENT_HEART_RATE)
+ public static final int RATE_PER_MINUTE_UNKNOWN = -1;
/** @hide */
@IntDef(prefix = { "EVENT_" }, value = {
@@ -636,10 +639,10 @@ public final class AmbientContextEvent implements Parcelable {
}
@DataClass.Generated(
- time = 1704895515931L,
+ time = 1705575046107L,
codegenVersion = "1.0.23",
sourceFile = "frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java",
- inputSignatures = "public static final int EVENT_UNKNOWN\npublic static final int EVENT_COUGH\npublic static final int EVENT_SNORE\npublic static final int EVENT_BACK_DOUBLE_TAP\npublic static final @android.app.ambientcontext.AmbientContextEvent.Event @android.annotation.FlaggedApi int EVENT_HEART_RATE\npublic static final int EVENT_VENDOR_WEARABLE_START\npublic static final java.lang.String KEY_VENDOR_WEARABLE_EVENT_NAME\nprivate static final int RATE_PER_MINUTE_UNKNOWN\npublic static final int LEVEL_UNKNOWN\npublic static final int LEVEL_LOW\npublic static final int LEVEL_MEDIUM_LOW\npublic static final int LEVEL_MEDIUM\npublic static final int LEVEL_MEDIUM_HIGH\npublic static final int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate final @android.annotation.NonNull android.os.PersistableBundle mVendorData\nprivate final @android.annotation.IntRange int mRatePerMinute\nprivate static int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultDensityLevel()\nprivate static android.os.PersistableBundle defaultVendorData()\nprivate static int defaultRatePerMinute()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+ inputSignatures = "public static final int EVENT_UNKNOWN\npublic static final int EVENT_COUGH\npublic static final int EVENT_SNORE\npublic static final int EVENT_BACK_DOUBLE_TAP\npublic static final @android.app.ambientcontext.AmbientContextEvent.Event @android.annotation.FlaggedApi int EVENT_HEART_RATE\npublic static final int EVENT_VENDOR_WEARABLE_START\npublic static final java.lang.String KEY_VENDOR_WEARABLE_EVENT_NAME\npublic static final @android.annotation.FlaggedApi int RATE_PER_MINUTE_UNKNOWN\npublic static final int LEVEL_UNKNOWN\npublic static final int LEVEL_LOW\npublic static final int LEVEL_MEDIUM_LOW\npublic static final int LEVEL_MEDIUM\npublic static final int LEVEL_MEDIUM_HIGH\npublic static final int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate final @android.annotation.NonNull android.os.PersistableBundle mVendorData\nprivate final @android.annotation.IntRange int mRatePerMinute\nprivate static int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static int defaultConfidenceLevel()\nprivate static int defaultDensityLevel()\nprivate static android.os.PersistableBundle defaultVendorData()\nprivate static int defaultRatePerMinute()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/app/usage/UsageEventsQuery.java b/core/java/android/app/usage/UsageEventsQuery.java
index 3cd292392694..df6324f744a8 100644
--- a/core/java/android/app/usage/UsageEventsQuery.java
+++ b/core/java/android/app/usage/UsageEventsQuery.java
@@ -29,9 +29,6 @@ import android.util.ArraySet;
import com.android.internal.util.ArrayUtils;
import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Set;
/**
* An Object-Oriented representation for a {@link UsageEvents} query.
@@ -77,19 +74,17 @@ public final class UsageEventsQuery implements Parcelable {
}
/**
- * Returns the set of usage event types for the query.
- * <em>Note: An empty set indicates query for all usage events. </em>
+ * Retrieves the usage event types for the query.
+ * <p>Note that an empty array indicates querying all usage event types, and it may
+ * cause additional system overhead when calling
+ * {@link UsageStatsManager#queryEvents(UsageEventsQuery)}. Apps are encouraged to
+ * provide a list of event types via {@link Builder#setEventTypes(int...)}</p>
+ *
+ * @return an array contains the usage event types that was previously set using
+ * {@link Builder#setEventTypes(int...)} or an empty array if no value has been set.
*/
- public @NonNull Set<Integer> getEventTypes() {
- if (ArrayUtils.isEmpty(mEventTypes)) {
- return Collections.emptySet();
- }
-
- HashSet<Integer> eventTypeSet = new HashSet<>();
- for (int eventType : mEventTypes) {
- eventTypeSet.add(eventType);
- }
- return eventTypeSet;
+ public @NonNull @Event.EventType int[] getEventTypes() {
+ return Arrays.copyOf(mEventTypes, mEventTypes.length);
}
/** @hide */
@@ -125,11 +120,6 @@ public final class UsageEventsQuery implements Parcelable {
}
};
- /** @hide */
- public int[] getEventTypeFilter() {
- return Arrays.copyOf(mEventTypes, mEventTypes.length);
- }
-
/**
* Builder for UsageEventsQuery.
*/
@@ -166,12 +156,25 @@ public final class UsageEventsQuery implements Parcelable {
}
/**
- * Specifies the list of usage event types to be included in the query.
- * @param eventTypes List of the usage event types. See {@link UsageEvents.Event}
+ * Sets the list of usage event types to be included in the query.
+ *
+ * <p>Note: </p> An empty array will be returned by
+ * {@link UsageEventsQuery#getEventTypes()} without calling this method, which indicates
+ * querying for all event types. Apps are encouraged to provide a list of event types.
+ * Only the matching types supplied will be used to query.
*
- * @throws llegalArgumentException if the event type is not valid.
+ * @param eventTypes the array of the usage event types. See {@link UsageEvents.Event}.
+ * @throws NullPointerException if {@code eventTypes} is {@code null} or empty.
+ * @throws IllegalArgumentException if any of event types are invalid.
+ * @see UsageEventsQuery#getEventTypes()
+ * @see UsageStatsManager#queryEvents(UsageEventsQuery)
*/
- public @NonNull Builder addEventTypes(@NonNull @Event.EventType int... eventTypes) {
+ public @NonNull Builder setEventTypes(@NonNull @Event.EventType int... eventTypes) {
+ if (eventTypes == null || eventTypes.length == 0) {
+ throw new NullPointerException("eventTypes is null or empty");
+ }
+
+ mEventTypes.clear();
for (int i = 0; i < eventTypes.length; i++) {
final int eventType = eventTypes[i];
if (eventType < Event.NONE || eventType > Event.MAX_EVENT_TYPE) {
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 85d223d01ee8..8df913a4ca63 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -602,14 +602,15 @@ public final class UsageStatsManager {
/**
* Query for events with specific UsageEventsQuery object.
+ *
* <em>Note: if the user's device is not in an unlocked state (as defined by
* {@link UserManager#isUserUnlocked()}), then {@code null} will be returned.</em>
*
* @param query The query object used to specify the query parameters.
- * @return A {@link UsageEvents}.
+ * @return A {@link UsageEvents} which contains the events matching the query parameters.
*/
@FlaggedApi(Flags.FLAG_FILTER_BASED_EVENT_QUERY_API)
- @NonNull
+ @Nullable
@RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)
public UsageEvents queryEvents(@NonNull UsageEventsQuery query) {
try {
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 879656a89233..672e343959cb 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -76,6 +76,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@@ -909,34 +910,20 @@ public final class CompanionDeviceManager {
}
/**
- * Listener for any changes to the list of attached transports.
- *
- * @see com.android.server.companion.transport.Transport
- *
- * @hide
- */
- public interface OnTransportsChangedListener {
- /**
- * Invoked when a transport is attached or detached.
- *
- * @param associations all the associations which have connected transports.
- */
- void onTransportsChanged(@NonNull List<AssociationInfo> associations);
- }
-
- /**
* Adds a listener for any changes to the list of attached transports.
- * {@link OnTransportsChangedListener#onTransportsChanged(List)} will be triggered with a list
- * of existing transports when a transport is detached or a new transport is attached.
+ * Registered listener will be triggered with a list of existing transports when a transport
+ * is detached or a new transport is attached.
*
+ * @param executor The executor which will be used to invoke the listener.
+ * @param listener Called when a transport is attached or detached. Contains the updated list of
+ * associations which have connected transports.
* @see com.android.server.companion.transport.Transport
- *
* @hide
*/
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void addOnTransportsChangedListener(
@NonNull @CallbackExecutor Executor executor,
- @NonNull OnTransportsChangedListener listener) {
+ @NonNull Consumer<List<AssociationInfo>> listener) {
final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
executor, listener);
try {
@@ -955,7 +942,7 @@ public final class CompanionDeviceManager {
*/
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void removeOnTransportsChangedListener(
- @NonNull OnTransportsChangedListener listener) {
+ @NonNull Consumer<List<AssociationInfo>> listener) {
final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
null, listener);
try {
@@ -983,28 +970,18 @@ public final class CompanionDeviceManager {
}
/**
- * Listener that triggers a callback when a message is received through a connected transport.
- *
- * @see #addOnMessageReceivedListener(Executor, int, OnMessageReceivedListener)
- *
- * @hide
- */
- public interface OnMessageReceivedListener {
- /**
- * Called when a message is received.
- */
- void onMessageReceived(int associationId, @NonNull byte[] data);
- }
-
- /**
- * Adds a listener to trigger callbacks when messages of given type are received.
+ * Adds a listener that triggers when messages of given type are received.
*
+ * @param executor The executor which will be used to invoke the listener.
+ * @param messageType Message type to be subscribed to.
+ * @param listener Called when a message is received. Contains the association ID of the message
+ * sender and the message payload as a byte array.
* @hide
*/
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void addOnMessageReceivedListener(
@NonNull @CallbackExecutor Executor executor, int messageType,
- @NonNull OnMessageReceivedListener listener) {
+ @NonNull BiConsumer<Integer, byte[]> listener) {
final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
executor, listener);
try {
@@ -1021,7 +998,7 @@ public final class CompanionDeviceManager {
*/
@RequiresPermission(android.Manifest.permission.USE_COMPANION_TRANSPORTS)
public void removeOnMessageReceivedListener(int messageType,
- @NonNull OnMessageReceivedListener listener) {
+ @NonNull BiConsumer<Integer, byte[]> listener) {
final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
null, listener);
try {
@@ -1596,34 +1573,34 @@ public final class CompanionDeviceManager {
private static class OnTransportsChangedListenerProxy
extends IOnTransportsChangedListener.Stub {
private final Executor mExecutor;
- private final OnTransportsChangedListener mListener;
+ private final Consumer<List<AssociationInfo>> mListener;
private OnTransportsChangedListenerProxy(Executor executor,
- OnTransportsChangedListener listener) {
+ Consumer<List<AssociationInfo>> listener) {
mExecutor = executor;
mListener = listener;
}
@Override
public void onTransportsChanged(@NonNull List<AssociationInfo> associations) {
- mExecutor.execute(() -> mListener.onTransportsChanged(associations));
+ mExecutor.execute(() -> mListener.accept(associations));
}
}
private static class OnMessageReceivedListenerProxy
extends IOnMessageReceivedListener.Stub {
private final Executor mExecutor;
- private final OnMessageReceivedListener mListener;
+ private final BiConsumer<Integer, byte[]> mListener;
private OnMessageReceivedListenerProxy(Executor executor,
- OnMessageReceivedListener listener) {
+ BiConsumer<Integer, byte[]> listener) {
mExecutor = executor;
mListener = listener;
}
@Override
public void onMessageReceived(int associationId, byte[] data) {
- mExecutor.execute(() -> mListener.onMessageReceived(associationId, data));
+ mExecutor.execute(() -> mListener.accept(associationId, data));
}
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4bc1237a55d1..249c0e434e78 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -4242,6 +4242,7 @@ public abstract class Context {
VIRTUALIZATION_SERVICE,
GRAMMATICAL_INFLECTION_SERVICE,
SECURITY_STATE_SERVICE,
+ //@hide: ECM_ENHANCED_CONFIRMATION_SERVICE,
})
@Retention(RetentionPolicy.SOURCE)
@@ -6527,6 +6528,18 @@ public abstract class Context {
public static final String SECURITY_STATE_SERVICE = "security_state";
/**
+ * Use with {@link #getSystemService(String)} to retrieve an
+ * {@link android.app.ecm.EnhancedConfirmationManager}.
+ *
+ * @see #getSystemService(String)
+ * @see android.app.ecm.EnhancedConfirmationManager
+ * @hide
+ */
+ @FlaggedApi(android.permission.flags.Flags.FLAG_ENHANCED_CONFIRMATION_MODE_APIS_ENABLED)
+ @SystemApi
+ public static final String ECM_ENHANCED_CONFIRMATION_SERVICE = "ecm_enhanced_confirmation";
+
+ /**
* Determine whether the given permission is allowed for a particular
* process and user ID running in the system.
*
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index 32ecb58ce241..1f25fd039dd8 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -80,7 +80,7 @@ interface IPackageInstaller {
long timeout);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})")
- void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle, int flags);
+ void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
void requestUnarchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 433226413917..f0efed97d8ed 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2352,7 +2352,6 @@ public class PackageInstaller {
* communicated.
*
* @param statusReceiver Callback used to notify when the operation is completed.
- * @param flags Flags for archiving. Can be 0 or {@link PackageManager#DELETE_SHOW_DIALOG}.
* @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not
* available to the caller or isn't archived.
*/
@@ -2360,12 +2359,11 @@ public class PackageInstaller {
Manifest.permission.DELETE_PACKAGES,
Manifest.permission.REQUEST_DELETE_PACKAGES})
@FlaggedApi(Flags.FLAG_ARCHIVING)
- public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver,
- @DeleteFlags int flags)
+ public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
throws PackageManager.NameNotFoundException {
try {
mInstaller.requestArchive(packageName, mInstallerPackageName, statusReceiver,
- new UserHandle(mUserId), flags);
+ new UserHandle(mUserId));
} catch (ParcelableException e) {
e.maybeRethrow(PackageManager.NameNotFoundException.class);
} catch (RemoteException e) {
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8e5e8250c85d..aabbe698703c 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2596,7 +2596,6 @@ public abstract class PackageManager {
DELETE_SYSTEM_APP,
DELETE_DONT_KILL_APP,
DELETE_CHATTY,
- DELETE_SHOW_DIALOG,
})
@Retention(RetentionPolicy.SOURCE)
public @interface DeleteFlags {}
@@ -2649,12 +2648,6 @@ public abstract class PackageManager {
public static final int DELETE_ARCHIVE = 0x00000010;
/**
- * Show a confirmation dialog to the user when app is being deleted.
- */
- @FlaggedApi(android.content.pm.Flags.FLAG_ARCHIVING)
- public static final int DELETE_SHOW_DIALOG = 0x00000020;
-
- /**
* Flag parameter for {@link #deletePackage} to indicate that package deletion
* should be chatty.
*
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 57749d43eb37..269c6c27821c 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -123,6 +123,7 @@ public final class UserProperties implements Parcelable {
* @hide
*/
@IntDef(prefix = "SHOW_IN_LAUNCHER_", value = {
+ SHOW_IN_LAUNCHER_UNKNOWN,
SHOW_IN_LAUNCHER_WITH_PARENT,
SHOW_IN_LAUNCHER_SEPARATE,
SHOW_IN_LAUNCHER_NO,
@@ -131,6 +132,13 @@ public final class UserProperties implements Parcelable {
public @interface ShowInLauncher {
}
/**
+ * Indicates that the show in launcher value for this profile is unknown or unsupported.
+ * @hide
+ */
+ @TestApi
+ @SuppressLint("UnflaggedApi") // b/306636213
+ public static final int SHOW_IN_LAUNCHER_UNKNOWN = -1;
+ /**
* Suggests that the launcher should show this user's apps in the main tab.
* That is, either this user is a full user, so its apps should be presented accordingly, or, if
* this user is a profile, then its apps should be shown alongside its parent's apps.
@@ -157,6 +165,7 @@ public final class UserProperties implements Parcelable {
* @hide
*/
@IntDef(prefix = "SHOW_IN_SETTINGS_", value = {
+ SHOW_IN_SETTINGS_UNKNOWN,
SHOW_IN_SETTINGS_WITH_PARENT,
SHOW_IN_SETTINGS_SEPARATE,
SHOW_IN_SETTINGS_NO,
@@ -165,6 +174,12 @@ public final class UserProperties implements Parcelable {
public @interface ShowInSettings {
}
/**
+ * Indicates that the show in settings value for this profile is unknown or unsupported.
+ * @hide
+ */
+ @SuppressLint("UnflaggedApi") // b/306636213
+ public static final int SHOW_IN_SETTINGS_UNKNOWN = -1;
+ /**
* Suggests that the Settings app should show this user's apps in the main tab.
* That is, either this user is a full user, so its apps should be presented accordingly, or, if
* this user is a profile, then its apps should be shown alongside its parent's apps.
@@ -309,6 +324,7 @@ public final class UserProperties implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "SHOW_IN_QUIET_MODE_",
value = {
+ SHOW_IN_QUIET_MODE_UNKNOWN,
SHOW_IN_QUIET_MODE_PAUSED,
SHOW_IN_QUIET_MODE_HIDDEN,
SHOW_IN_QUIET_MODE_DEFAULT,
@@ -318,6 +334,12 @@ public final class UserProperties implements Parcelable {
}
/**
+ * Indicates that the show in quiet mode value for this profile is unknown.
+ */
+ @SuppressLint("UnflaggedApi") // b/306636213
+ public static final int SHOW_IN_QUIET_MODE_UNKNOWN = -1;
+
+ /**
* Indicates that the profile should still be visible in quiet mode but should be shown as
* paused (e.g. by greying out its icons).
*/
@@ -347,6 +369,7 @@ public final class UserProperties implements Parcelable {
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "SHOW_IN_SHARING_SURFACES_",
value = {
+ SHOW_IN_SHARING_SURFACES_UNKNOWN,
SHOW_IN_SHARING_SURFACES_SEPARATE,
SHOW_IN_SHARING_SURFACES_WITH_PARENT,
SHOW_IN_SHARING_SURFACES_NO,
@@ -356,6 +379,12 @@ public final class UserProperties implements Parcelable {
}
/**
+ * Indicates that the show in launcher value for this profile is unknown or unsupported.
+ */
+ @SuppressLint("UnflaggedApi") // b/306636213
+ public static final int SHOW_IN_SHARING_SURFACES_UNKNOWN = SHOW_IN_LAUNCHER_UNKNOWN;
+
+ /**
* Indicates that the profile data and apps should be shown in sharing surfaces intermixed with
* parent user's data and apps.
*/
@@ -379,7 +408,8 @@ public final class UserProperties implements Parcelable {
*
* @hide
*/
- @IntDef(prefix = {"CROSS_PROFILE_CONTENT_SHARING_STRATEGY_"}, value = {
+ @IntDef(prefix = {"CROSS_PROFILE_CONTENT_SHARING_"}, value = {
+ CROSS_PROFILE_CONTENT_SHARING_UNKNOWN,
CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION,
CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT
})
@@ -388,6 +418,13 @@ public final class UserProperties implements Parcelable {
}
/**
+ * Signifies that cross-profile content sharing strategy, both to and from this profile, is
+ * unknown/unsupported.
+ */
+ @SuppressLint("UnflaggedApi") // b/306636213
+ public static final int CROSS_PROFILE_CONTENT_SHARING_UNKNOWN = -1;
+
+ /**
* Signifies that cross-profile content sharing strategy, both to and from this profile, should
* not be delegated to any other user/profile.
* For ex:
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index c7797c719e2c..7c5d3054c945 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -70,4 +70,11 @@ flag {
namespace: "profile_experiences"
description: "Add support for Private Space in resolver sheet"
bug: "307515485"
+}
+
+flag {
+ name: "move_quiet_mode_operations_to_separate_thread"
+ namespace: "profile_experiences"
+ description: "Move the quiet mode operations, happening on a background thread today, to a separate thread."
+ bug: "320483504"
} \ No newline at end of file
diff --git a/core/java/android/content/res/FontScaleConverter.java b/core/java/android/content/res/FontScaleConverter.java
index 088949e7eec2..f4312a905110 100644
--- a/core/java/android/content/res/FontScaleConverter.java
+++ b/core/java/android/content/res/FontScaleConverter.java
@@ -17,7 +17,9 @@
package android.content.res;
+import android.annotation.AnyThread;
import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
/**
* A converter for non-linear font scaling. Converts font sizes given in "sp" dimensions to a
@@ -40,4 +42,35 @@ public interface FontScaleConverter {
* Converts a dimension in "dp" back to "sp".
*/
float convertDpToSp(float dp);
+
+ /**
+ * Returns true if non-linear font scaling curves would be in effect for the given scale, false
+ * if the scaling would follow a linear curve or for no scaling.
+ *
+ * <p>Example usage: {@code
+ * isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)}
+ */
+ @AnyThread
+ static boolean isNonLinearFontScalingActive(float fontScale) {
+ return FontScaleConverterFactory.isNonLinearFontScalingActive(fontScale);
+ }
+
+ /**
+ * Finds a matching FontScaleConverter for the given fontScale factor.
+ *
+ * Generally you shouldn't need this; you can use {@link
+ * android.util.TypedValue#applyDimension(int, float, DisplayMetrics)} directly and it will do
+ * the scaling conversion for you. Dimens and resources loaded from XML will also be
+ * automatically converted. But for UI frameworks or other situations where you need to do the
+ * conversion without an Android Context, you can use this method.
+ *
+ * @param fontScale the scale factor, usually from {@link Configuration#fontScale}.
+ *
+ * @return a converter for the given scale, or null if non-linear scaling should not be used.
+ */
+ @Nullable
+ @AnyThread
+ static FontScaleConverter forScale(float fontScale) {
+ return FontScaleConverterFactory.forScale(fontScale);
+ }
}
diff --git a/core/java/android/content/res/FontScaleConverterFactory.java b/core/java/android/content/res/FontScaleConverterFactory.java
index 5d31cc0f0243..cbe4c62d7069 100644
--- a/core/java/android/content/res/FontScaleConverterFactory.java
+++ b/core/java/android/content/res/FontScaleConverterFactory.java
@@ -17,7 +17,6 @@
package android.content.res;
import android.annotation.AnyThread;
-import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.MathUtils;
@@ -32,8 +31,9 @@ import com.android.internal.annotations.VisibleForTesting;
* android.util.TypedValue#applyDimension(int, float, DisplayMetrics)} directly and it will do the
* scaling conversion for you. But for UI frameworks or other situations where you need to do the
* conversion without an Android Context, you can use this class.
+ *
+ * @hide
*/
-@FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
public class FontScaleConverterFactory {
private static final float SCALE_KEY_MULTIPLIER = 100f;
@@ -124,7 +124,6 @@ public class FontScaleConverterFactory {
* <p>Example usage:
* <code>isNonLinearFontScalingActive(getResources().getConfiguration().fontScale)</code>
*/
- @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
@AnyThread
public static boolean isNonLinearFontScalingActive(float fontScale) {
return fontScale >= sMinScaleBeforeCurvesApplied;
@@ -137,7 +136,6 @@ public class FontScaleConverterFactory {
*
* @return a converter for the given scale, or null if non-linear scaling should not be used.
*/
- @FlaggedApi(Flags.FLAG_FONT_SCALE_CONVERTER_PUBLIC)
@Nullable
@AnyThread
public static FontScaleConverter forScale(float fontScale) {
diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig
index f876eebe64c1..90cd4718fcee 100644
--- a/core/java/android/credentials/flags.aconfig
+++ b/core/java/android/credentials/flags.aconfig
@@ -33,4 +33,18 @@ flag {
name: "new_settings_ui"
description: "Enables new settings UI for VIC"
bug: "315209085"
+}
+
+flag {
+ namespace: "credential_manager"
+ name: "selector_ui_improvements_enabled"
+ description: "Enables Credential Selector UI improvements for VIC"
+ bug: "319448437"
+}
+
+flag {
+ namespace: "credential_manager"
+ name: "configurable_selector_ui_enabled"
+ description: "Enables OEM configurable Credential Selector UI"
+ bug: "319448437"
} \ No newline at end of file
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index a0f4d8df0bd5..c0424dbeb813 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -16,6 +16,7 @@
package android.hardware.biometrics;
+import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG;
import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -25,6 +26,7 @@ import static android.hardware.biometrics.Flags.FLAG_GET_OP_ID_CRYPTO_OBJECT;
import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
import android.annotation.CallbackExecutor;
+import android.annotation.DrawableRes;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -33,6 +35,7 @@ import android.annotation.RequiresPermission;
import android.annotation.TestApi;
import android.content.Context;
import android.content.DialogInterface;
+import android.graphics.Bitmap;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.Binder;
@@ -160,6 +163,45 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
}
/**
+ * Optional: Sets the drawable resource of the logo that will be shown on the prompt.
+ *
+ * <p> Note that using this method is not recommended in most scenarios because the calling
+ * application's icon will be used by default. Setting the logo is intended for large
+ * bundled applications that perform a wide range of functions and need to show distinct
+ * icons for each function.
+ *
+ * @param logoRes A drawable resource of the logo that will be shown on the prompt.
+ * @return This builder.
+ */
+ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @NonNull
+ public BiometricPrompt.Builder setLogo(@DrawableRes int logoRes) {
+ mPromptInfo.setLogoRes(logoRes);
+ return this;
+ }
+
+ /**
+ * Optional: Sets the bitmap drawable of the logo that will be shown on the prompt.
+ *
+ * <p> Note that using this method is not recommended in most scenarios because the calling
+ * application's icon will be used by default. Setting the logo is intended for large
+ * bundled applications that perform a wide range of functions and need to show distinct
+ * icons for each function.
+ *
+ * @param logoBitmap A bitmap drawable of the logo that will be shown on the prompt.
+ * @return This builder.
+ */
+ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @NonNull
+ public BiometricPrompt.Builder setLogo(@NonNull Bitmap logoBitmap) {
+ mPromptInfo.setLogoBitmap(logoBitmap);
+ return this;
+ }
+
+
+ /**
* Required: Sets the title that will be shown on the prompt.
* @param title The title to display.
* @return This builder.
@@ -676,6 +718,34 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan
}
/**
+ * Gets the drawable resource of the logo for the prompt, as set by
+ * {@link Builder#setLogo(int)}. Currently for system applications use only.
+ *
+ * @return The drawable resource of the logo, or -1 if the prompt has no logo resource set.
+ */
+ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @DrawableRes
+ public int getLogoRes() {
+ return mPromptInfo.getLogoRes();
+ }
+
+ /**
+ * Gets the logo bitmap for the prompt, as set by {@link Builder#setLogo(Bitmap)}. Currently for
+ * system applications use only.
+ *
+ * @return The logo bitmap of the prompt, or null if the prompt has no logo bitmap set.
+ */
+ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
+ @RequiresPermission(MANAGE_BIOMETRIC_DIALOG)
+ @Nullable
+ public Bitmap getLogoBitmap() {
+ return mPromptInfo.getLogoBitmap();
+ }
+
+
+
+ /**
* Gets the title for the prompt, as set by {@link Builder#setTitle(CharSequence)}.
* @return The title of the prompt, which is guaranteed to be non-null.
*/
diff --git a/core/java/android/hardware/biometrics/PromptContentListItem.java b/core/java/android/hardware/biometrics/PromptContentItem.java
index fa3783d6d874..c47b37aca2ea 100644
--- a/core/java/android/hardware/biometrics/PromptContentListItem.java
+++ b/core/java/android/hardware/biometrics/PromptContentItem.java
@@ -21,9 +21,9 @@ import static android.hardware.biometrics.Flags.FLAG_CUSTOM_BIOMETRIC_PROMPT;
import android.annotation.FlaggedApi;
/**
- * A list item shown on {@link PromptVerticalListContentView}.
+ * An item shown on {@link PromptContentView}.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-public interface PromptContentListItem {
+public interface PromptContentItem {
}
diff --git a/core/java/android/hardware/biometrics/PromptContentListItemBulletedText.java b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
index c31f8a63bcb8..c5e5a8076747 100644
--- a/core/java/android/hardware/biometrics/PromptContentListItemBulletedText.java
+++ b/core/java/android/hardware/biometrics/PromptContentItemBulletedText.java
@@ -27,7 +27,7 @@ import android.os.Parcelable;
* A list item with bulleted text shown on {@link PromptVerticalListContentView}.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-public final class PromptContentListItemBulletedText implements PromptContentListItemParcelable {
+public final class PromptContentItemBulletedText implements PromptContentItemParcelable {
private final CharSequence mText;
/**
@@ -35,7 +35,7 @@ public final class PromptContentListItemBulletedText implements PromptContentLis
*
* @param text The text of this list item.
*/
- public PromptContentListItemBulletedText(@NonNull CharSequence text) {
+ public PromptContentItemBulletedText(@NonNull CharSequence text) {
mText = text;
}
@@ -67,15 +67,15 @@ public final class PromptContentListItemBulletedText implements PromptContentLis
* @see Parcelable.Creator
*/
@NonNull
- public static final Creator<PromptContentListItemBulletedText> CREATOR = new Creator<>() {
+ public static final Creator<PromptContentItemBulletedText> CREATOR = new Creator<>() {
@Override
- public PromptContentListItemBulletedText createFromParcel(Parcel in) {
- return new PromptContentListItemBulletedText(in.readCharSequence());
+ public PromptContentItemBulletedText createFromParcel(Parcel in) {
+ return new PromptContentItemBulletedText(in.readCharSequence());
}
@Override
- public PromptContentListItemBulletedText[] newArray(int size) {
- return new PromptContentListItemBulletedText[size];
+ public PromptContentItemBulletedText[] newArray(int size) {
+ return new PromptContentItemBulletedText[size];
}
};
}
diff --git a/core/java/android/hardware/biometrics/PromptContentListItemParcelable.java b/core/java/android/hardware/biometrics/PromptContentItemParcelable.java
index 15271f003c50..668912cfdf24 100644
--- a/core/java/android/hardware/biometrics/PromptContentListItemParcelable.java
+++ b/core/java/android/hardware/biometrics/PromptContentItemParcelable.java
@@ -22,9 +22,9 @@ import android.annotation.FlaggedApi;
import android.os.Parcelable;
/**
- * A parcelable {@link PromptContentListItem}.
+ * A parcelable {@link PromptContentItem}.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-sealed interface PromptContentListItemParcelable extends PromptContentListItem, Parcelable
- permits PromptContentListItemPlainText, PromptContentListItemBulletedText {
+sealed interface PromptContentItemParcelable extends PromptContentItem, Parcelable
+ permits PromptContentItemPlainText, PromptContentItemBulletedText {
}
diff --git a/core/java/android/hardware/biometrics/PromptContentListItemPlainText.java b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
index d72a7586d6ad..6434c5975c12 100644
--- a/core/java/android/hardware/biometrics/PromptContentListItemPlainText.java
+++ b/core/java/android/hardware/biometrics/PromptContentItemPlainText.java
@@ -27,7 +27,7 @@ import android.os.Parcelable;
* A list item with plain text shown on {@link PromptVerticalListContentView}.
*/
@FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT)
-public final class PromptContentListItemPlainText implements PromptContentListItemParcelable {
+public final class PromptContentItemPlainText implements PromptContentItemParcelable {
private final CharSequence mText;
/**
@@ -35,7 +35,7 @@ public final class PromptContentListItemPlainText implements PromptContentListIt
*
* @param text The text of this list item.
*/
- public PromptContentListItemPlainText(@NonNull CharSequence text) {
+ public PromptContentItemPlainText(@NonNull CharSequence text) {
mText = text;
}
@@ -67,15 +67,15 @@ public final class PromptContentListItemPlainText implements PromptContentListIt
* @see Parcelable.Creator
*/
@NonNull
- public static final Creator<PromptContentListItemPlainText> CREATOR = new Creator<>() {
+ public static final Creator<PromptContentItemPlainText> CREATOR = new Creator<>() {
@Override
- public PromptContentListItemPlainText createFromParcel(Parcel in) {
- return new PromptContentListItemPlainText(in.readCharSequence());
+ public PromptContentItemPlainText createFromParcel(Parcel in) {
+ return new PromptContentItemPlainText(in.readCharSequence());
}
@Override
- public PromptContentListItemPlainText[] newArray(int size) {
- return new PromptContentListItemPlainText[size];
+ public PromptContentItemPlainText[] newArray(int size) {
+ return new PromptContentItemPlainText[size];
}
};
}
diff --git a/core/java/android/hardware/biometrics/PromptInfo.java b/core/java/android/hardware/biometrics/PromptInfo.java
index c73ebd4dbe76..d788b37c781d 100644
--- a/core/java/android/hardware/biometrics/PromptInfo.java
+++ b/core/java/android/hardware/biometrics/PromptInfo.java
@@ -16,8 +16,10 @@
package android.hardware.biometrics;
+import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Bitmap;
import android.os.Parcel;
import android.os.Parcelable;
@@ -30,6 +32,8 @@ import java.util.List;
*/
public class PromptInfo implements Parcelable {
+ @DrawableRes private int mLogoRes = -1;
+ @Nullable private Bitmap mLogoBitmap;
@NonNull private CharSequence mTitle;
private boolean mUseDefaultTitle;
@Nullable private CharSequence mSubtitle;
@@ -56,6 +60,8 @@ public class PromptInfo implements Parcelable {
}
PromptInfo(Parcel in) {
+ mLogoRes = in.readInt();
+ mLogoBitmap = in.readTypedObject(Bitmap.CREATOR);
mTitle = in.readCharSequence();
mUseDefaultTitle = in.readBoolean();
mSubtitle = in.readCharSequence();
@@ -98,6 +104,8 @@ public class PromptInfo implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mLogoRes);
+ dest.writeTypedObject(mLogoBitmap, 0);
dest.writeCharSequence(mTitle);
dest.writeBoolean(mUseDefaultTitle);
dest.writeCharSequence(mSubtitle);
@@ -156,9 +164,30 @@ public class PromptInfo implements Parcelable {
}
return false;
}
+
+ /**
+ * Returns whether MANAGE_BIOMETRIC_DIALOG is contained.
+ */
+ public boolean containsManageBioApiConfigurations() {
+ if (mLogoRes != -1) {
+ return true;
+ } else if (mLogoBitmap != null) {
+ return true;
+ }
+ return false;
+ }
// LINT.ThenChange(frameworks/base/core/java/android/hardware/biometrics/BiometricPrompt.java)
// Setters
+ public void setLogoRes(@DrawableRes int logoRes) {
+ mLogoRes = logoRes;
+ checkOnlyOneLogoSet();
+ }
+
+ public void setLogoBitmap(@NonNull Bitmap logoBitmap) {
+ mLogoBitmap = logoBitmap;
+ checkOnlyOneLogoSet();
+ }
public void setTitle(CharSequence title) {
mTitle = title;
@@ -244,6 +273,14 @@ public class PromptInfo implements Parcelable {
}
// Getters
+ @DrawableRes
+ public int getLogoRes() {
+ return mLogoRes;
+ }
+
+ public Bitmap getLogoBitmap() {
+ return mLogoBitmap;
+ }
public CharSequence getTitle() {
return mTitle;
@@ -337,4 +374,11 @@ public class PromptInfo implements Parcelable {
public boolean isShowEmergencyCallButton() {
return mShowEmergencyCallButton;
}
+
+ private void checkOnlyOneLogoSet() {
+ if (mLogoRes != -1 && mLogoBitmap != null) {
+ throw new IllegalStateException(
+ "Exclusively one of logo resource or logo bitmap can be set");
+ }
+ }
}
diff --git a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
index f3cb189a5df5..f3e62907d845 100644
--- a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
+++ b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java
@@ -40,9 +40,9 @@ import java.util.List;
* .setSubTitle(...)
* .setContentView(new PromptVerticalListContentView.Builder()
* .setDescription("test description")
- * .addListItem(new PromptContentListItemPlainText("test item 1"))
- * .addListItem(new PromptContentListItemPlainText("test item 2"))
- * .addListItem(new PromptContentListItemBulletedText("test item 3"))
+ * .addListItem(new PromptContentItemPlainText("test item 1"))
+ * .addListItem(new PromptContentItemPlainText("test item 2"))
+ * .addListItem(new PromptContentItemBulletedText("test item 3"))
* .build())
* .build();
* </pre>
@@ -51,11 +51,11 @@ import java.util.List;
public final class PromptVerticalListContentView implements PromptContentViewParcelable {
private static final int MAX_ITEM_NUMBER = 20;
private static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640;
- private final List<PromptContentListItemParcelable> mContentList;
+ private final List<PromptContentItemParcelable> mContentList;
private final CharSequence mDescription;
private PromptVerticalListContentView(
- @NonNull List<PromptContentListItemParcelable> contentList,
+ @NonNull List<PromptContentItemParcelable> contentList,
@NonNull CharSequence description) {
mContentList = contentList;
mDescription = description;
@@ -63,8 +63,8 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
private PromptVerticalListContentView(Parcel in) {
mContentList = in.readArrayList(
- PromptContentListItemParcelable.class.getClassLoader(),
- PromptContentListItemParcelable.class);
+ PromptContentItemParcelable.class.getClassLoader(),
+ PromptContentItemParcelable.class);
mDescription = in.readCharSequence();
}
@@ -94,13 +94,13 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
}
/**
- * Gets the list of ListItem on the content view, as set by
- * {@link PromptVerticalListContentView.Builder#addListItem(PromptContentListItem)}.
+ * Gets the list of items on the content view, as set by
+ * {@link PromptVerticalListContentView.Builder#addListItem(PromptContentItem)}.
*
* @return The item list on the content view.
*/
@NonNull
- public List<PromptContentListItem> getListItems() {
+ public List<PromptContentItem> getListItems() {
return new ArrayList<>(mContentList);
}
@@ -142,7 +142,7 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
* A builder that collects arguments to be shown on the vertical list view.
*/
public static final class Builder {
- private final List<PromptContentListItemParcelable> mContentList = new ArrayList<>();
+ private final List<PromptContentItemParcelable> mContentList = new ArrayList<>();
private CharSequence mDescription;
/**
@@ -159,28 +159,50 @@ public final class PromptVerticalListContentView implements PromptContentViewPar
/**
* Optional: Adds a list item in the current row. Maximum {@value MAX_ITEM_NUMBER} items in
- * total.
+ * total. The maximum length for each item is {@value MAX_EACH_ITEM_CHARACTER_NUMBER}
+ * characters.
*
* @param listItem The list item view to display
* @return This builder.
*/
@NonNull
- public Builder addListItem(@NonNull PromptContentListItem listItem) {
+ public Builder addListItem(@NonNull PromptContentItem listItem) {
if (doesListItemExceedsCharLimit(listItem)) {
throw new IllegalStateException(
"The character number of list item exceeds "
+ MAX_EACH_ITEM_CHARACTER_NUMBER);
}
- mContentList.add((PromptContentListItemParcelable) listItem);
+ mContentList.add((PromptContentItemParcelable) listItem);
return this;
}
- private boolean doesListItemExceedsCharLimit(PromptContentListItem listItem) {
- if (listItem instanceof PromptContentListItemPlainText) {
- return ((PromptContentListItemPlainText) listItem).getText().length()
+
+ /**
+ * Optional: Adds a list item in the current row. Maximum {@value MAX_ITEM_NUMBER} items in
+ * total. The maximum length for each item is {@value MAX_EACH_ITEM_CHARACTER_NUMBER}
+ * characters.
+ *
+ * @param listItem The list item view to display
+ * @param index The position at which to add the item
+ * @return This builder.
+ */
+ @NonNull
+ public Builder addListItem(@NonNull PromptContentItem listItem, int index) {
+ if (doesListItemExceedsCharLimit(listItem)) {
+ throw new IllegalStateException(
+ "The character number of list item exceeds "
+ + MAX_EACH_ITEM_CHARACTER_NUMBER);
+ }
+ mContentList.add(index, (PromptContentItemParcelable) listItem);
+ return this;
+ }
+
+ private boolean doesListItemExceedsCharLimit(PromptContentItem listItem) {
+ if (listItem instanceof PromptContentItemPlainText) {
+ return ((PromptContentItemPlainText) listItem).getText().length()
> MAX_EACH_ITEM_CHARACTER_NUMBER;
- } else if (listItem instanceof PromptContentListItemBulletedText) {
- return ((PromptContentListItemBulletedText) listItem).getText().length()
+ } else if (listItem instanceof PromptContentItemBulletedText) {
+ return ((PromptContentItemBulletedText) listItem).getText().length()
> MAX_EACH_ITEM_CHARACTER_NUMBER;
} else {
return false;
diff --git a/core/java/android/hardware/radio/TEST_MAPPING b/core/java/android/hardware/radio/TEST_MAPPING
new file mode 100644
index 000000000000..ee4eeb634c84
--- /dev/null
+++ b/core/java/android/hardware/radio/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "imports": [
+ {
+ "path": "frameworks/base/core/tests/BroadcastRadioTests"
+ }
+ ]
+}
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 25fba60b9bb5..b9bb059bef42 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -17,9 +17,11 @@
package android.os;
import static android.os.Flags.FLAG_STATE_OF_HEALTH_PUBLIC;
+import static android.os.Flags.FLAG_BATTERY_PART_STATUS_API;
import android.Manifest.permission;
import android.annotation.FlaggedApi;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
@@ -236,6 +238,31 @@ public class BatteryManager {
public static final int CHARGING_POLICY_ADAPTIVE_LONGLIFE =
OsProtoEnums.CHARGING_POLICY_ADAPTIVE_LONGLIFE; // = 4
+ // values for "battery part status" property
+ /**
+ * Battery part status is not supported.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int PART_STATUS_UNSUPPORTED = 0;
+
+ /**
+ * Battery is the original device battery.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int PART_STATUS_ORIGINAL = 1;
+
+ /**
+ * Battery has been replaced.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int PART_STATUS_REPLACED = 2;
+
/** @hide */
@SuppressLint("UnflaggedApi") // TestApi without associated feature.
@TestApi
@@ -366,6 +393,32 @@ public class BatteryManager {
@FlaggedApi(FLAG_STATE_OF_HEALTH_PUBLIC)
public static final int BATTERY_PROPERTY_STATE_OF_HEALTH = 10;
+ /**
+ * Battery part serial number.
+ *
+ * <p class="note">
+ * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+ *
+ * @hide
+ */
+ @RequiresPermission(permission.BATTERY_STATS)
+ @SystemApi
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_PROPERTY_SERIAL_NUMBER = 11;
+
+ /**
+ * Battery part status from a BATTERY_PART_STATUS_* value.
+ *
+ * <p class="note">
+ * The sender must hold the {@link android.Manifest.permission#BATTERY_STATS} permission.
+ *
+ * @hide
+ */
+ @RequiresPermission(permission.BATTERY_STATS)
+ @SystemApi
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public static final int BATTERY_PROPERTY_PART_STATUS = 12;
+
private final Context mContext;
private final IBatteryStats mBatteryStats;
private final IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar;
@@ -431,6 +484,25 @@ public class BatteryManager {
}
/**
+ * Same as queryProperty, but for strings.
+ */
+ private String queryStringProperty(int id) {
+ if (mBatteryPropertiesRegistrar == null) {
+ return null;
+ }
+
+ try {
+ BatteryProperty prop = new BatteryProperty();
+ if (mBatteryPropertiesRegistrar.getProperty(id, prop) == 0) {
+ return prop.getString();
+ }
+ return null;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Return the value of a battery property of integer type.
*
* @param id identifier of the requested property
@@ -464,6 +536,21 @@ public class BatteryManager {
}
/**
+ * Return the value of a battery property of String type. If the
+ * platform does not provide the property queried, this value will
+ * be null.
+ *
+ * @param id identifier of the requested property.
+ *
+ * @return the property value, or null if not supported.
+ */
+ @Nullable
+ @FlaggedApi(FLAG_BATTERY_PART_STATUS_API)
+ public String getStringProperty(int id) {
+ return queryStringProperty(id);
+ }
+
+ /**
* Return true if the plugType given is wired
* @param plugType {@link #BATTERY_PLUGGED_AC}, {@link #BATTERY_PLUGGED_USB},
* or {@link #BATTERY_PLUGGED_WIRELESS}
diff --git a/core/java/android/os/BatteryProperty.java b/core/java/android/os/BatteryProperty.java
index b40988a938bc..464577f58241 100644
--- a/core/java/android/os/BatteryProperty.java
+++ b/core/java/android/os/BatteryProperty.java
@@ -28,12 +28,14 @@ import android.os.Parcelable;
*/
public class BatteryProperty implements Parcelable {
private long mValueLong;
+ private String mValueString;
/**
* @hide
*/
public BatteryProperty() {
mValueLong = Long.MIN_VALUE;
+ mValueString = null;
}
/**
@@ -46,14 +48,23 @@ public class BatteryProperty implements Parcelable {
/**
* @hide
*/
+ public String getString() {
+ return mValueString;
+ }
+
+ /**
+ * @hide
+ */
public void setLong(long val) {
mValueLong = val;
}
- /*
- * Parcel read/write code must be kept in sync with
- * frameworks/native/services/batteryservice/BatteryProperty.cpp
+ /**
+ * @hide
*/
+ public void setString(String val) {
+ mValueString = val;
+ }
private BatteryProperty(Parcel p) {
readFromParcel(p);
@@ -61,10 +72,12 @@ public class BatteryProperty implements Parcelable {
public void readFromParcel(Parcel p) {
mValueLong = p.readLong();
+ mValueString = p.readString8();
}
public void writeToParcel(Parcel p, int flags) {
p.writeLong(mValueLong);
+ p.writeString8(mValueString);
}
public static final @android.annotation.NonNull Parcelable.Creator<BatteryProperty> CREATOR
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 0db90bff48fd..82518bfbfd8d 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -106,3 +106,11 @@ flag {
bug: "305311707"
is_fixed_read_only: true
}
+
+flag {
+ name: "battery_part_status_api"
+ namespace: "phoenix"
+ description: "Feature flag for adding Health HAL v3 APIs."
+ is_fixed_read_only: true
+ bug: "309792384"
+}
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index bced21730a34..39b6aeb814f6 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -45,7 +45,8 @@ flag {
}
flag {
- name: "enhanced_confirmation_mode_apis"
+ name: "enhanced_confirmation_mode_apis_enabled"
+ is_fixed_read_only: true
namespace: "permissions"
description: "enable enhanced confirmation mode apis"
bug: "310220212"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7d84bb390853..58159c2b5693 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3176,15 +3176,7 @@ public final class Settings {
}
public void destroy() {
- try {
- // If this process is the system server process, mArray is the same object as
- // the memory int array kept inside SettingsProvider, so skipping the close()
- if (!Settings.isInSystemServer() && !mArray.isClosed()) {
- mArray.close();
- }
- } catch (IOException e) {
- Log.e(TAG, "Error closing backing array", e);
- }
+ maybeCloseGenerationArray(mArray);
}
@Override
@@ -3197,6 +3189,21 @@ public final class Settings {
}
}
+ private static void maybeCloseGenerationArray(@Nullable MemoryIntArray array) {
+ if (array == null) {
+ return;
+ }
+ try {
+ // If this process is the system server process, the MemoryIntArray received from Parcel
+ // is the same object as the one kept inside SettingsProvider, so skipping the close().
+ if (!Settings.isInSystemServer() && !array.isClosed()) {
+ array.close();
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error closing the generation tracking array", e);
+ }
+ }
+
private static final class ContentProviderHolder {
private final Object mLock = new Object();
@@ -3496,6 +3503,8 @@ public final class Settings {
mGenerationTrackers.put(name, new GenerationTracker(name,
array, index, generation,
mGenerationTrackerErrorHandler));
+ } else {
+ maybeCloseGenerationArray(array);
}
}
if (mGenerationTrackers.get(name) != null
@@ -3733,6 +3742,8 @@ public final class Settings {
new GenerationTracker(prefix, array, index, generation,
mGenerationTrackerErrorHandler));
currentGeneration = generation;
+ } else {
+ maybeCloseGenerationArray(array);
}
}
if (mGenerationTrackers.get(prefix) != null && currentGeneration
@@ -7310,6 +7321,28 @@ public final class Settings {
"bluetooth_le_broadcast_app_source_name";
/**
+ * This is used by LocalBluetoothLeBroadcast to downgrade the broadcast quality to improve
+ * compatibility.
+ *
+ * <ul>
+ * <li>0 = false
+ * <li>1 = true
+ * </ul>
+ *
+ * @hide
+ */
+ public static final String BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY =
+ "bluetooth_le_broadcast_improve_compatibility";
+
+ /**
+ * This is used by LocalBluetoothLeBroadcast to store the fallback active device address.
+ *
+ * @hide
+ */
+ public static final String BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS =
+ "bluetooth_le_broadcast_fallback_active_device_address";
+
+ /**
* Ringtone routing value for hearing aid. It routes ringtone to hearing aid or device
* speaker.
* <ul>
diff --git a/core/java/android/provider/TEST_MAPPING b/core/java/android/provider/TEST_MAPPING
index 8b4a99e38299..d5ac7a7de461 100644
--- a/core/java/android/provider/TEST_MAPPING
+++ b/core/java/android/provider/TEST_MAPPING
@@ -28,5 +28,10 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsDeviceConfigTestCases"
+ }
]
}
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 30524a1132fa..1994058441d5 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -53,7 +53,7 @@ flag {
flag {
name: "frp_enforcement"
- namespace: "android_hw_security"
+ namespace: "hardware_backed_security"
description: "This flag controls whether PDB enforces FRP"
bug: "290312729"
is_fixed_read_only: true
diff --git a/core/java/android/service/notification/ZenDeviceEffects.java b/core/java/android/service/notification/ZenDeviceEffects.java
index 03ebae5c5199..90049e6a934a 100644
--- a/core/java/android/service/notification/ZenDeviceEffects.java
+++ b/core/java/android/service/notification/ZenDeviceEffects.java
@@ -20,7 +20,6 @@ import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.TestApi;
import android.app.Flags;
import android.os.Parcel;
import android.os.Parcelable;
@@ -37,8 +36,8 @@ import java.util.Objects;
@FlaggedApi(Flags.FLAG_MODES_API)
public final class ZenDeviceEffects implements Parcelable {
- /** Used to track which rule variables have been modified by the user.
- * Should be checked against the bitmask {@link #getUserModifiedFields()}.
+ /**
+ * Enum for the user-modifiable fields in this object.
* @hide
*/
@IntDef(flag = true, prefix = { "FIELD_" }, value = {
@@ -59,52 +58,42 @@ public final class ZenDeviceEffects implements Parcelable {
/**
* @hide
*/
- @TestApi
public static final int FIELD_GRAYSCALE = 1 << 0;
/**
* @hide
*/
- @TestApi
public static final int FIELD_SUPPRESS_AMBIENT_DISPLAY = 1 << 1;
/**
* @hide
*/
- @TestApi
public static final int FIELD_DIM_WALLPAPER = 1 << 2;
/**
* @hide
*/
- @TestApi
public static final int FIELD_NIGHT_MODE = 1 << 3;
/**
* @hide
*/
- @TestApi
public static final int FIELD_DISABLE_AUTO_BRIGHTNESS = 1 << 4;
/**
* @hide
*/
- @TestApi
public static final int FIELD_DISABLE_TAP_TO_WAKE = 1 << 5;
/**
* @hide
*/
- @TestApi
public static final int FIELD_DISABLE_TILT_TO_WAKE = 1 << 6;
/**
* @hide
*/
- @TestApi
public static final int FIELD_DISABLE_TOUCH = 1 << 7;
/**
* @hide
*/
- @TestApi
public static final int FIELD_MINIMIZE_RADIO_USAGE = 1 << 8;
/**
* @hide
*/
- @TestApi
public static final int FIELD_MAXIMIZE_DOZE = 1 << 9;
private final boolean mGrayscale;
@@ -119,13 +108,10 @@ public final class ZenDeviceEffects implements Parcelable {
private final boolean mMinimizeRadioUsage;
private final boolean mMaximizeDoze;
- private final @ModifiableField int mUserModifiedFields; // Bitwise representation
-
private ZenDeviceEffects(boolean grayscale, boolean suppressAmbientDisplay,
boolean dimWallpaper, boolean nightMode, boolean disableAutoBrightness,
boolean disableTapToWake, boolean disableTiltToWake, boolean disableTouch,
- boolean minimizeRadioUsage, boolean maximizeDoze,
- @ModifiableField int userModifiedFields) {
+ boolean minimizeRadioUsage, boolean maximizeDoze) {
mGrayscale = grayscale;
mSuppressAmbientDisplay = suppressAmbientDisplay;
mDimWallpaper = dimWallpaper;
@@ -136,7 +122,6 @@ public final class ZenDeviceEffects implements Parcelable {
mDisableTouch = disableTouch;
mMinimizeRadioUsage = minimizeRadioUsage;
mMaximizeDoze = maximizeDoze;
- mUserModifiedFields = userModifiedFields;
}
@Override
@@ -153,15 +138,14 @@ public final class ZenDeviceEffects implements Parcelable {
&& this.mDisableTiltToWake == that.mDisableTiltToWake
&& this.mDisableTouch == that.mDisableTouch
&& this.mMinimizeRadioUsage == that.mMinimizeRadioUsage
- && this.mMaximizeDoze == that.mMaximizeDoze
- && this.mUserModifiedFields == that.mUserModifiedFields;
+ && this.mMaximizeDoze == that.mMaximizeDoze;
}
@Override
public int hashCode() {
return Objects.hash(mGrayscale, mSuppressAmbientDisplay, mDimWallpaper, mNightMode,
mDisableAutoBrightness, mDisableTapToWake, mDisableTiltToWake, mDisableTouch,
- mMinimizeRadioUsage, mMaximizeDoze, mUserModifiedFields);
+ mMinimizeRadioUsage, mMaximizeDoze);
}
@Override
@@ -177,11 +161,11 @@ public final class ZenDeviceEffects implements Parcelable {
if (mDisableTouch) effects.add("disableTouch");
if (mMinimizeRadioUsage) effects.add("minimizeRadioUsage");
if (mMaximizeDoze) effects.add("maximizeDoze");
- return "[" + String.join(", ", effects) + "]"
- + " userModifiedFields: " + modifiedFieldsToString(mUserModifiedFields);
+ return "[" + String.join(", ", effects) + "]";
}
- private String modifiedFieldsToString(int bitmask) {
+ /** @hide */
+ public static String fieldsToString(@ModifiableField int bitmask) {
ArrayList<String> modified = new ArrayList<>();
if ((bitmask & FIELD_GRAYSCALE) != 0) {
modified.add("FIELD_GRAYSCALE");
@@ -312,7 +296,7 @@ public final class ZenDeviceEffects implements Parcelable {
return new ZenDeviceEffects(in.readBoolean(),
in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(),
in.readBoolean(), in.readBoolean(), in.readBoolean(), in.readBoolean(),
- in.readBoolean(), in.readInt());
+ in.readBoolean());
}
@Override
@@ -321,16 +305,6 @@ public final class ZenDeviceEffects implements Parcelable {
}
};
- /**
- * Gets the bitmask representing which fields are user modified. Bits are set using
- * {@link ModifiableField}.
- * @hide
- */
- @TestApi
- public @ModifiableField int getUserModifiedFields() {
- return mUserModifiedFields;
- }
-
@Override
public int describeContents() {
return 0;
@@ -348,7 +322,6 @@ public final class ZenDeviceEffects implements Parcelable {
dest.writeBoolean(mDisableTouch);
dest.writeBoolean(mMinimizeRadioUsage);
dest.writeBoolean(mMaximizeDoze);
- dest.writeInt(mUserModifiedFields);
}
/** Builder class for {@link ZenDeviceEffects} objects. */
@@ -365,7 +338,6 @@ public final class ZenDeviceEffects implements Parcelable {
private boolean mDisableTouch;
private boolean mMinimizeRadioUsage;
private boolean mMaximizeDoze;
- private @ModifiableField int mUserModifiedFields;
/**
* Instantiates a new {@link ZenPolicy.Builder} with all effects set to default (disabled).
@@ -388,7 +360,6 @@ public final class ZenDeviceEffects implements Parcelable {
mDisableTouch = zenDeviceEffects.shouldDisableTouch();
mMinimizeRadioUsage = zenDeviceEffects.shouldMinimizeRadioUsage();
mMaximizeDoze = zenDeviceEffects.shouldMaximizeDoze();
- mUserModifiedFields = zenDeviceEffects.mUserModifiedFields;
}
/**
@@ -510,24 +481,13 @@ public final class ZenDeviceEffects implements Parcelable {
return this;
}
- /**
- * Sets the bitmask representing which fields are user modified. See the FIELD_ constants.
- * @hide
- */
- @TestApi
- @NonNull
- public Builder setUserModifiedFields(@ModifiableField int userModifiedFields) {
- mUserModifiedFields = userModifiedFields;
- return this;
- }
-
/** Builds a {@link ZenDeviceEffects} object based on the builder's state. */
@NonNull
public ZenDeviceEffects build() {
return new ZenDeviceEffects(mGrayscale,
mSuppressAmbientDisplay, mDimWallpaper, mNightMode, mDisableAutoBrightness,
mDisableTapToWake, mDisableTiltToWake, mDisableTouch, mMinimizeRadioUsage,
- mMaximizeDoze, mUserModifiedFields);
+ mMaximizeDoze);
}
}
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index d4d602da0071..c479877fe98e 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -206,7 +206,7 @@ public class ZenModeConfig implements Parcelable {
private static final String ALLOW_ATT_CONV = "convos";
private static final String ALLOW_ATT_CONV_FROM = "convosFrom";
private static final String ALLOW_ATT_CHANNELS = "priorityChannels";
- private static final String USER_MODIFIED_FIELDS = "policyUserModifiedFields";
+ private static final String POLICY_USER_MODIFIED_FIELDS = "policyUserModifiedFields";
private static final String DISALLOW_TAG = "disallow";
private static final String DISALLOW_ATT_VISUAL_EFFECTS = "visualEffects";
private static final String STATE_TAG = "state";
@@ -806,6 +806,9 @@ public class ZenModeConfig implements Parcelable {
rt.triggerDescription = parser.getAttributeValue(null, RULE_ATT_TRIGGER_DESC);
rt.type = safeInt(parser, RULE_ATT_TYPE, AutomaticZenRule.TYPE_UNKNOWN);
rt.userModifiedFields = safeInt(parser, RULE_ATT_USER_MODIFIED_FIELDS, 0);
+ rt.zenPolicyUserModifiedFields = safeInt(parser, POLICY_USER_MODIFIED_FIELDS, 0);
+ rt.zenDeviceEffectsUserModifiedFields = safeInt(parser,
+ DEVICE_EFFECT_USER_MODIFIED_FIELDS, 0);
Long deletionInstant = tryParseLong(
parser.getAttributeValue(null, RULE_ATT_DELETION_INSTANT), null);
if (deletionInstant != null) {
@@ -858,6 +861,9 @@ public class ZenModeConfig implements Parcelable {
}
out.attributeInt(null, RULE_ATT_TYPE, rule.type);
out.attributeInt(null, RULE_ATT_USER_MODIFIED_FIELDS, rule.userModifiedFields);
+ out.attributeInt(null, POLICY_USER_MODIFIED_FIELDS, rule.zenPolicyUserModifiedFields);
+ out.attributeInt(null, DEVICE_EFFECT_USER_MODIFIED_FIELDS,
+ rule.zenDeviceEffectsUserModifiedFields);
if (rule.deletionInstant != null) {
out.attributeLong(null, RULE_ATT_DELETION_INSTANT,
rule.deletionInstant.toEpochMilli());
@@ -924,7 +930,6 @@ public class ZenModeConfig implements Parcelable {
builder.allowPriorityChannels(channels == ZenPolicy.STATE_ALLOW);
policySet = true;
}
- builder.setUserModifiedFields(safeInt(parser, USER_MODIFIED_FIELDS, 0));
}
if (calls != ZenPolicy.PEOPLE_TYPE_UNSET) {
@@ -1037,7 +1042,6 @@ public class ZenModeConfig implements Parcelable {
if (Flags.modesApi()) {
writeZenPolicyState(ALLOW_ATT_CHANNELS, policy.getPriorityChannels(), out);
- out.attributeInt(null, USER_MODIFIED_FIELDS, policy.getUserModifiedFields());
}
}
@@ -1083,7 +1087,6 @@ public class ZenModeConfig implements Parcelable {
.setShouldMinimizeRadioUsage(
safeBoolean(parser, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE, false))
.setShouldMaximizeDoze(safeBoolean(parser, DEVICE_EFFECT_MAXIMIZE_DOZE, false))
- .setUserModifiedFields(safeInt(parser, DEVICE_EFFECT_USER_MODIFIED_FIELDS, 0))
.build();
return deviceEffects.hasEffects() ? deviceEffects : null;
@@ -1108,8 +1111,6 @@ public class ZenModeConfig implements Parcelable {
writeBooleanIfTrue(out, DEVICE_EFFECT_MINIMIZE_RADIO_USAGE,
deviceEffects.shouldMinimizeRadioUsage());
writeBooleanIfTrue(out, DEVICE_EFFECT_MAXIMIZE_DOZE, deviceEffects.shouldMaximizeDoze());
- out.attributeInt(null, DEVICE_EFFECT_USER_MODIFIED_FIELDS,
- deviceEffects.getUserModifiedFields());
}
private static void writeBooleanIfTrue(TypedXmlSerializer out, String att, boolean value)
@@ -2041,7 +2042,9 @@ public class ZenModeConfig implements Parcelable {
public String triggerDescription;
public String iconResName;
public boolean allowManualInvocation;
- public int userModifiedFields;
+ @AutomaticZenRule.ModifiableField public int userModifiedFields;
+ @ZenPolicy.ModifiableField public int zenPolicyUserModifiedFields;
+ @ZenDeviceEffects.ModifiableField public int zenDeviceEffectsUserModifiedFields;
@Nullable public Instant deletionInstant; // Only set on deleted rules.
public ZenRule() { }
@@ -2076,6 +2079,8 @@ public class ZenModeConfig implements Parcelable {
triggerDescription = source.readString();
type = source.readInt();
userModifiedFields = source.readInt();
+ zenPolicyUserModifiedFields = source.readInt();
+ zenDeviceEffectsUserModifiedFields = source.readInt();
if (source.readInt() == 1) {
deletionInstant = Instant.ofEpochMilli(source.readLong());
}
@@ -2083,15 +2088,21 @@ public class ZenModeConfig implements Parcelable {
}
/**
- * @see AutomaticZenRule#canUpdate()
+ * Whether this ZenRule can be updated by an app. In general, rules that have been
+ * customized by the user cannot be further updated by an app, with some exceptions:
+ * <ul>
+ * <li>Non user-configurable fields, like type, icon, configurationActivity, etc.
+ * <li>Name, if the name was not specifically modified by the user (to support language
+ * switches).
+ * </ul>
*/
@FlaggedApi(Flags.FLAG_MODES_API)
public boolean canBeUpdatedByApp() {
// The rule is considered updateable if its bitmask has no user modifications, and
// the bitmasks of the policy and device effects have no modification.
return userModifiedFields == 0
- && (zenPolicy == null || zenPolicy.getUserModifiedFields() == 0)
- && (zenDeviceEffects == null || zenDeviceEffects.getUserModifiedFields() == 0);
+ && zenPolicyUserModifiedFields == 0
+ && zenDeviceEffectsUserModifiedFields == 0;
}
@Override
@@ -2139,6 +2150,8 @@ public class ZenModeConfig implements Parcelable {
dest.writeString(triggerDescription);
dest.writeInt(type);
dest.writeInt(userModifiedFields);
+ dest.writeInt(zenPolicyUserModifiedFields);
+ dest.writeInt(zenDeviceEffectsUserModifiedFields);
if (deletionInstant != null) {
dest.writeInt(1);
dest.writeLong(deletionInstant.toEpochMilli());
@@ -2173,8 +2186,20 @@ public class ZenModeConfig implements Parcelable {
.append(",allowManualInvocation=").append(allowManualInvocation)
.append(",iconResName=").append(iconResName)
.append(",triggerDescription=").append(triggerDescription)
- .append(",type=").append(type)
- .append(",userModifiedFields=").append(userModifiedFields);
+ .append(",type=").append(type);
+ if (userModifiedFields != 0) {
+ sb.append(",userModifiedFields=")
+ .append(AutomaticZenRule.fieldsToString(userModifiedFields));
+ }
+ if (zenPolicyUserModifiedFields != 0) {
+ sb.append(",zenPolicyUserModifiedFields=")
+ .append(ZenPolicy.fieldsToString(zenPolicyUserModifiedFields));
+ }
+ if (zenDeviceEffectsUserModifiedFields != 0) {
+ sb.append(",zenDeviceEffectsUserModifiedFields=")
+ .append(ZenDeviceEffects.fieldsToString(
+ zenDeviceEffectsUserModifiedFields));
+ }
if (deletionInstant != null) {
sb.append(",deletionInstant=").append(deletionInstant);
}
@@ -2238,6 +2263,9 @@ public class ZenModeConfig implements Parcelable {
&& Objects.equals(other.triggerDescription, triggerDescription)
&& other.type == type
&& other.userModifiedFields == userModifiedFields
+ && other.zenPolicyUserModifiedFields == zenPolicyUserModifiedFields
+ && other.zenDeviceEffectsUserModifiedFields
+ == zenDeviceEffectsUserModifiedFields
&& Objects.equals(other.deletionInstant, deletionInstant);
}
@@ -2250,7 +2278,8 @@ public class ZenModeConfig implements Parcelable {
return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
component, configurationActivity, pkg, id, enabler, zenPolicy,
zenDeviceEffects, modified, allowManualInvocation, iconResName,
- triggerDescription, type, userModifiedFields, deletionInstant);
+ triggerDescription, type, userModifiedFields, zenPolicyUserModifiedFields,
+ zenDeviceEffectsUserModifiedFields, deletionInstant);
}
return Objects.hash(enabled, snoozing, name, zenMode, conditionId, condition,
component, configurationActivity, pkg, id, enabler, zenPolicy, modified);
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index 2cda7c882cdb..fb491d010f54 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -45,8 +45,8 @@ import java.util.Objects;
*/
public final class ZenPolicy implements Parcelable {
- /** Used to track which rule variables have been modified by the user.
- * Should be checked against the bitmask {@link #getUserModifiedFields()}.
+ /**
+ * Enum for the user-modifiable fields in this object.
* @hide
*/
@IntDef(flag = true, prefix = { "FIELD_" }, value = {
@@ -76,7 +76,6 @@ public final class ZenPolicy implements Parcelable {
* the same time.
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_MESSAGES = 1 << 0;
/**
@@ -84,7 +83,6 @@ public final class ZenPolicy implements Parcelable {
* the same time.
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_CALLS = 1 << 1;
/**
@@ -92,13 +90,11 @@ public final class ZenPolicy implements Parcelable {
* set at the same time.
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_CONVERSATIONS = 1 << 2;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_ALLOW_CHANNELS = 1 << 3;
/**
@@ -109,73 +105,61 @@ public final class ZenPolicy implements Parcelable {
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_PRIORITY_CATEGORY_EVENTS = 1 << 5;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS = 1 << 6;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_PRIORITY_CATEGORY_ALARMS = 1 << 7;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_PRIORITY_CATEGORY_MEDIA = 1 << 8;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_PRIORITY_CATEGORY_SYSTEM = 1 << 9;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_FULL_SCREEN_INTENT = 1 << 10;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_LIGHTS = 1 << 11;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_PEEK = 1 << 12;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_STATUS_BAR = 1 << 13;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_BADGE = 1 << 14;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_AMBIENT = 1 << 15;
/**
* @hide
*/
- @TestApi
@FlaggedApi(Flags.FLAG_MODES_API)
public static final int FIELD_VISUAL_EFFECT_NOTIFICATION_LIST = 1 << 16;
@@ -186,7 +170,6 @@ public final class ZenPolicy implements Parcelable {
private @ConversationSenders int mConversationSenders = CONVERSATION_SENDERS_UNSET;
@FlaggedApi(Flags.FLAG_MODES_API)
private @ChannelType int mAllowChannels = CHANNEL_POLICY_UNSET;
- private final @ModifiableField int mUserModifiedFields; // Bitwise representation
/** @hide */
@IntDef(prefix = { "PRIORITY_CATEGORY_" }, value = {
@@ -388,22 +371,19 @@ public final class ZenPolicy implements Parcelable {
public ZenPolicy() {
mPriorityCategories = new ArrayList<>(Collections.nCopies(NUM_PRIORITY_CATEGORIES, 0));
mVisualEffects = new ArrayList<>(Collections.nCopies(NUM_VISUAL_EFFECTS, 0));
- mUserModifiedFields = 0;
}
/** @hide */
@FlaggedApi(Flags.FLAG_MODES_API)
public ZenPolicy(List<Integer> priorityCategories, List<Integer> visualEffects,
@PeopleType int priorityMessages, @PeopleType int priorityCalls,
- @ConversationSenders int conversationSenders, @ChannelType int allowChannels,
- @ModifiableField int userModifiedFields) {
+ @ConversationSenders int conversationSenders, @ChannelType int allowChannels) {
mPriorityCategories = priorityCategories;
mVisualEffects = visualEffects;
mPriorityMessages = priorityMessages;
mPriorityCalls = priorityCalls;
mConversationSenders = conversationSenders;
mAllowChannels = allowChannels;
- mUserModifiedFields = userModifiedFields;
}
/**
@@ -633,8 +613,6 @@ public final class ZenPolicy implements Parcelable {
* is not set, it is (@link STATE_UNSET} and will not change the current set policy.
*/
public static final class Builder {
- private @ModifiableField int mUserModifiedFields;
-
private ZenPolicy mZenPolicy;
public Builder() {
@@ -649,9 +627,6 @@ public final class ZenPolicy implements Parcelable {
public Builder(@Nullable ZenPolicy policy) {
if (policy != null) {
mZenPolicy = policy.copy();
- if (Flags.modesApi()) {
- mUserModifiedFields = policy.mUserModifiedFields;
- }
} else {
mZenPolicy = new ZenPolicy();
}
@@ -662,11 +637,10 @@ public final class ZenPolicy implements Parcelable {
*/
public @NonNull ZenPolicy build() {
if (Flags.modesApi()) {
- return new ZenPolicy(new ArrayList<Integer>(mZenPolicy.mPriorityCategories),
- new ArrayList<Integer>(mZenPolicy.mVisualEffects),
+ return new ZenPolicy(new ArrayList<>(mZenPolicy.mPriorityCategories),
+ new ArrayList<>(mZenPolicy.mVisualEffects),
mZenPolicy.mPriorityMessages, mZenPolicy.mPriorityCalls,
- mZenPolicy.mConversationSenders, mZenPolicy.mAllowChannels,
- mUserModifiedFields);
+ mZenPolicy.mConversationSenders, mZenPolicy.mAllowChannels);
} else {
return mZenPolicy.copy();
}
@@ -1025,28 +999,6 @@ public final class ZenPolicy implements Parcelable {
mZenPolicy.mAllowChannels = allow ? CHANNEL_POLICY_PRIORITY : CHANNEL_POLICY_NONE;
return this;
}
-
- /**
- * Sets the user modified fields bitmask.
- * @hide
- */
- @TestApi
- @FlaggedApi(Flags.FLAG_MODES_API)
- public @NonNull Builder setUserModifiedFields(@ModifiableField int userModifiedFields) {
- mUserModifiedFields = userModifiedFields;
- return this;
- }
- }
-
- /**
- Gets the bitmask representing which fields are user modified. Bits are set using
- * {@link ModifiableField}.
- * @hide
- */
- @TestApi
- @FlaggedApi(Flags.FLAG_MODES_API)
- public @ModifiableField int getUserModifiedFields() {
- return mUserModifiedFields;
}
@Override
@@ -1063,7 +1015,6 @@ public final class ZenPolicy implements Parcelable {
dest.writeInt(mConversationSenders);
if (Flags.modesApi()) {
dest.writeInt(mAllowChannels);
- dest.writeInt(mUserModifiedFields);
}
}
@@ -1079,7 +1030,7 @@ public final class ZenPolicy implements Parcelable {
trimList(source.readArrayList(Integer.class.getClassLoader(),
Integer.class), NUM_VISUAL_EFFECTS),
source.readInt(), source.readInt(), source.readInt(),
- source.readInt(), source.readInt()
+ source.readInt()
);
} else {
policy = new ZenPolicy();
@@ -1114,14 +1065,12 @@ public final class ZenPolicy implements Parcelable {
conversationTypeToString(mConversationSenders));
if (Flags.modesApi()) {
sb.append(", allowChannels=").append(channelTypeToString(mAllowChannels));
- sb.append(", userModifiedFields=")
- .append(modifiedFieldsToString(mUserModifiedFields));
}
return sb.append('}').toString();
}
- @FlaggedApi(Flags.FLAG_MODES_API)
- private String modifiedFieldsToString(@ModifiableField int bitmask) {
+ /** @hide */
+ public static String fieldsToString(@ModifiableField int bitmask) {
ArrayList<String> modified = new ArrayList<>();
if ((bitmask & FIELD_MESSAGES) != 0) {
modified.add("FIELD_MESSAGES");
@@ -1332,8 +1281,7 @@ public final class ZenPolicy implements Parcelable {
&& other.mPriorityMessages == mPriorityMessages
&& other.mConversationSenders == mConversationSenders;
if (Flags.modesApi()) {
- return eq && other.mAllowChannels == mAllowChannels
- && other.mUserModifiedFields == mUserModifiedFields;
+ return eq && other.mAllowChannels == mAllowChannels;
}
return eq;
}
@@ -1342,7 +1290,7 @@ public final class ZenPolicy implements Parcelable {
public int hashCode() {
if (Flags.modesApi()) {
return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls,
- mPriorityMessages, mConversationSenders, mAllowChannels, mUserModifiedFields);
+ mPriorityMessages, mConversationSenders, mAllowChannels);
}
return Objects.hash(mPriorityCategories, mVisualEffects, mPriorityCalls, mPriorityMessages,
mConversationSenders);
diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java
index adc54f5b5a8c..f2bdbf67e76e 100644
--- a/core/java/android/service/voice/VisualQueryDetector.java
+++ b/core/java/android/service/voice/VisualQueryDetector.java
@@ -325,7 +325,7 @@ public class VisualQueryDetector {
Slog.v(TAG, "BinderCallback#onQueryDetected");
Binder.withCleanCallingIdentity(() -> {
synchronized (mLock) {
- mCallback.onQueryDetected(partialQuery);
+ mExecutor.execute(()->mCallback.onQueryDetected(partialQuery));
}
});
}
@@ -335,7 +335,7 @@ public class VisualQueryDetector {
Slog.v(TAG, "BinderCallback#onQueryFinished");
Binder.withCleanCallingIdentity(() -> {
synchronized (mLock) {
- mCallback.onQueryFinished();
+ mExecutor.execute(()->mCallback.onQueryFinished());
}
});
}
@@ -345,7 +345,7 @@ public class VisualQueryDetector {
Slog.v(TAG, "BinderCallback#onQueryRejected");
Binder.withCleanCallingIdentity(() -> {
synchronized (mLock) {
- mCallback.onQueryRejected();
+ mExecutor.execute(()->mCallback.onQueryRejected());
}
});
}
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index cf1156db55e5..fb57921b1529 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -1681,6 +1681,10 @@ public class PhoneStateListener {
@EmergencyCallbackModeStopReason int reason) {
// not support. Can't override. Use TelephonyCallback.
}
+
+ public final void onSimultaneousCallingStateChanged(int[] subIds) {
+ // not supported on the deprecated interface - Use TelephonyCallback instead
+ }
}
private void log(String s) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 19bcf28d6b83..dc6a035a8176 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -18,6 +18,7 @@ package android.telephony;
import android.Manifest;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
@@ -33,15 +34,19 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IPhoneStateListener;
+import com.android.internal.telephony.flags.Flags;
import dalvik.system.VMRuntime;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
/**
* A callback class for monitoring changes in specific telephony states
@@ -627,6 +632,18 @@ public class TelephonyCallback {
public static final int EVENT_EMERGENCY_CALLBACK_MODE_CHANGED = 40;
/**
+ * Event for listening to changes in simultaneous cellular calling subscriptions.
+ *
+ * @see SimultaneousCellularCallingSupportListener
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @SystemApi
+ public static final int EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED = 41;
+
+ /**
* @hide
*/
@IntDef(prefix = {"EVENT_"}, value = {
@@ -669,7 +686,8 @@ public class TelephonyCallback {
EVENT_LINK_CAPACITY_ESTIMATE_CHANGED,
EVENT_TRIGGER_NOTIFY_ANBR,
EVENT_MEDIA_QUALITY_STATUS_CHANGED,
- EVENT_EMERGENCY_CALLBACK_MODE_CHANGED
+ EVENT_EMERGENCY_CALLBACK_MODE_CHANGED,
+ EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED
})
@Retention(RetentionPolicy.SOURCE)
public @interface TelephonyEvent {
@@ -1373,6 +1391,44 @@ public class TelephonyCallback {
}
/**
+ * Interface for listening to changes in the simultaneous cellular calling state for active
+ * cellular subscriptions.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+ @SystemApi
+ public interface SimultaneousCellularCallingSupportListener {
+ /**
+ * Notify the Listener that the subscriptions available for simultaneous <b>cellular</b>
+ * calling have changed.
+ * <p>
+ * If we have an ongoing <b>cellular</b> call on one subscription in this Set, a
+ * simultaneous incoming or outgoing <b>cellular</b> call is possible on any of the
+ * subscriptions in this Set. On a traditional Dual Sim Dual Standby device, simultaneous
+ * calling is not possible between subscriptions, where on a Dual Sim Dual Active device,
+ * simultaneous calling may be possible between subscriptions in certain network conditions.
+ * <p>
+ * Note: This listener only tracks the capability of the modem to perform simultaneous
+ * cellular calls and does not track the simultaneous calling state of scenarios based on
+ * multiple IMS registration over multiple transports (WiFi/Internet calling).
+ * <p>
+ * Note: This listener fires for all changes to cellular calling subscriptions independent
+ * of which subscription it is registered on.
+ *
+ * @param simultaneousCallingSubscriptionIds The Set of subscription IDs that support
+ * simultaneous calling. If there is an ongoing call on a subscription in this Set, then a
+ * simultaneous incoming or outgoing call is only possible for other subscriptions in this
+ * Set. If there is an ongoing call on a subscription that is not in this Set, then
+ * simultaneous calling is not possible at the current time.
+ *
+ */
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ void onSimultaneousCellularCallingSubscriptionsChanged(
+ @NonNull Set<Integer> simultaneousCallingSubscriptionIds);
+ }
+
+ /**
* Interface for call attributes listener.
*
* @hide
@@ -1976,6 +2032,17 @@ public class TelephonyCallback {
allowedNetworkType)));
}
+ public void onSimultaneousCallingStateChanged(int[] subIds) {
+ SimultaneousCellularCallingSupportListener listener =
+ (SimultaneousCellularCallingSupportListener) mTelephonyCallbackWeakRef.get();
+ if (listener == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(
+ () -> listener.onSimultaneousCellularCallingSubscriptionsChanged(
+ Arrays.stream(subIds).boxed().collect(Collectors.toSet()))));
+ }
+
public void onLinkCapacityEstimateChanged(
List<LinkCapacityEstimate> linkCapacityEstimateList) {
LinkCapacityEstimateChangedListener listener =
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 886727ea43ef..8935ab38451a 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -16,9 +16,11 @@
package android.telephony;
import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
@@ -50,6 +52,7 @@ import com.android.internal.telephony.ICarrierConfigChangeListener;
import com.android.internal.telephony.ICarrierPrivilegesCallback;
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
import com.android.internal.telephony.ITelephonyRegistry;
+import com.android.server.telecom.flags.Flags;
import java.lang.ref.WeakReference;
import java.util.Arrays;
@@ -66,11 +69,13 @@ import java.util.stream.Collectors;
* or {@link PhoneCapability} changed. This might trigger callback from applications side through
* {@link android.telephony.PhoneStateListener}
*
- * TODO: limit API access to only carrier apps with certain permissions or apps running on
+ * Limit API access to only carrier apps with certain permissions or apps running on
* privileged UID.
*
* @hide
*/
+@SystemApi
+@FlaggedApi(Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
public class TelephonyRegistryManager {
private static final String TAG = "TelephonyRegistryManager";
@@ -120,6 +125,7 @@ public class TelephonyRegistryManager {
* @param listener an instance of {@link SubscriptionManager.OnSubscriptionsChangedListener}
* with onSubscriptionsChanged overridden.
* @param executor the executor that will execute callbacks.
+ * @hide
*/
public void addOnSubscriptionsChangedListener(
@NonNull SubscriptionManager.OnSubscriptionsChangedListener listener,
@@ -155,6 +161,7 @@ public class TelephonyRegistryManager {
* invoke the listener fails.
*
* @param listener that is to be unregistered.
+ * @hide
*/
public void removeOnSubscriptionsChangedListener(
@NonNull SubscriptionManager.OnSubscriptionsChangedListener listener) {
@@ -180,6 +187,7 @@ public class TelephonyRegistryManager {
* {@link SubscriptionManager.OnOpportunisticSubscriptionsChangedListener} with
* onOpportunisticSubscriptionsChanged overridden.
* @param executor an Executor that will execute callbacks.
+ * @hide
*/
public void addOnOpportunisticSubscriptionsChangedListener(
@NonNull SubscriptionManager.OnOpportunisticSubscriptionsChangedListener listener,
@@ -221,6 +229,7 @@ public class TelephonyRegistryManager {
* listener fails.
*
* @param listener that is to be unregistered.
+ * @hide
*/
public void removeOnOpportunisticSubscriptionsChangedListener(
@NonNull SubscriptionManager.OnOpportunisticSubscriptionsChangedListener listener) {
@@ -252,6 +261,7 @@ public class TelephonyRegistryManager {
* @param listener Listener providing callback
* @param events Events
* @param notifyNow Whether to notify instantly
+ * @hide
*/
public void listenFromListener(int subId, @NonNull boolean renounceFineLocationAccess,
@NonNull boolean renounceCoarseLocationAccess, @NonNull String pkg,
@@ -318,6 +328,7 @@ public class TelephonyRegistryManager {
* @param active Whether the carrier network change is or shortly will be
* active. Set this value to true to begin showing alternative UI and false to stop.
* @see TelephonyManager#hasCarrierPrivileges
+ * @hide
*/
public void notifyCarrierNetworkChange(boolean active) {
try {
@@ -344,6 +355,7 @@ public class TelephonyRegistryManager {
* @param active whether the carrier network change is or shortly will be active. Set this value
* to true to begin showing alternative UI and false to stop.
* @see TelephonyManager#hasCarrierPrivileges
+ * @hide
*/
public void notifyCarrierNetworkChange(int subscriptionId, boolean active) {
try {
@@ -362,6 +374,7 @@ public class TelephonyRegistryManager {
* @param subId for which call state changed.
* @param state latest call state. e.g, offhook, ringing
* @param incomingNumber incoming phone number.
+ * @hide
*/
public void notifyCallStateChanged(int slotIndex, int subId, @CallState int state,
@Nullable String incomingNumber) {
@@ -380,6 +393,7 @@ public class TelephonyRegistryManager {
* @param incomingNumber incoming phone number.
* @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public void notifyCallStateChangedForAllSubscriptions(@CallState int state,
@Nullable String incomingNumber) {
@@ -424,6 +438,7 @@ public class TelephonyRegistryManager {
* subId is invalid.
* @param subId for which the service state changed.
* @param state service state e.g, in service, out of service or roaming status.
+ * @hide
*/
public void notifyServiceStateChanged(int slotIndex, int subId, @NonNull ServiceState state) {
try {
@@ -441,6 +456,7 @@ public class TelephonyRegistryManager {
* subId is invalid.
* @param subId for which the signalstrength changed.
* @param signalStrength e.g, signalstrength level {@see SignalStrength#getLevel()}
+ * @hide
*/
public void notifySignalStrengthChanged(int slotIndex, int subId,
@NonNull SignalStrength signalStrength) {
@@ -461,6 +477,7 @@ public class TelephonyRegistryManager {
* @param subId for which message waiting indicator changed.
* @param msgWaitingInd {@code true} indicates there is message-waiting indicator, {@code false}
* otherwise.
+ * @hide
*/
public void notifyMessageWaitingChanged(int slotIndex, int subId, boolean msgWaitingInd) {
try {
@@ -477,6 +494,7 @@ public class TelephonyRegistryManager {
* @param subId for which call forwarding status changed.
* @param callForwardInd {@code true} indicates there is call forwarding, {@code false}
* otherwise.
+ * @hide
*/
public void notifyCallForwardingChanged(int subId, boolean callForwardInd) {
try {
@@ -493,6 +511,7 @@ public class TelephonyRegistryManager {
* @param subId for which data activity state changed.
* @param dataActivityType indicates the latest data activity type e.g. {@link
* TelephonyManager#DATA_ACTIVITY_IN}
+ * @hide
*/
public void notifyDataActivityChanged(int subId, @DataActivityType int dataActivityType) {
try {
@@ -511,6 +530,7 @@ public class TelephonyRegistryManager {
* @param subId for which data activity state changed.
* @param dataActivityType indicates the latest data activity type e.g. {@link
* TelephonyManager#DATA_ACTIVITY_IN}
+ * @hide
*/
public void notifyDataActivityChanged(int slotIndex, int subId,
@DataActivityType int dataActivityType) {
@@ -532,6 +552,7 @@ public class TelephonyRegistryManager {
*
* @see PreciseDataConnectionState
* @see TelephonyManager#DATA_DISCONNECTED
+ * @hide
*/
public void notifyDataConnectionForSubscriber(int slotIndex, int subId,
@NonNull PreciseDataConnectionState preciseState) {
@@ -552,6 +573,7 @@ public class TelephonyRegistryManager {
* @param subId for which call quality state changed.
* @param callQuality Information about call quality e.g, call quality level
* @param networkType associated with this data connection. e.g, LTE
+ * @hide
*/
public void notifyCallQualityChanged(int slotIndex, int subId, @NonNull CallQuality callQuality,
@NetworkType int networkType) {
@@ -573,6 +595,7 @@ public class TelephonyRegistryManager {
* {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_JITTER_INT}
*
* @param status media quality status
+ * @hide
*/
public void notifyMediaQualityStatusChanged(
int slotIndex, int subId, @NonNull MediaQualityStatus status) {
@@ -590,6 +613,7 @@ public class TelephonyRegistryManager {
* @param slotIndex for which emergency number list changed. Can be derived from subId except
* when subId is invalid.
* @param subId for which emergency number list changed.
+ * @hide
*/
public void notifyEmergencyNumberList( int slotIndex, int subId) {
try {
@@ -602,14 +626,16 @@ public class TelephonyRegistryManager {
/**
* Notify outgoing emergency call.
- * @param phoneId Sender phone ID.
+ * @param simSlotIndex Sender phone ID.
* @param subId Sender subscription ID.
* @param emergencyNumber Emergency number.
+ * @hide
*/
- public void notifyOutgoingEmergencyCall(int phoneId, int subId,
+ @SystemApi
+ public void notifyOutgoingEmergencyCall(int simSlotIndex, int subId,
@NonNull EmergencyNumber emergencyNumber) {
try {
- sRegistry.notifyOutgoingEmergencyCall(phoneId, subId, emergencyNumber);
+ sRegistry.notifyOutgoingEmergencyCall(simSlotIndex, subId, emergencyNumber);
} catch (RemoteException ex) {
// system process is dead
throw ex.rethrowFromSystemServer();
@@ -621,6 +647,7 @@ public class TelephonyRegistryManager {
* @param phoneId Sender phone ID.
* @param subId Sender subscription ID.
* @param emergencyNumber Emergency number.
+ * @hide
*/
public void notifyOutgoingEmergencySms(int phoneId, int subId,
@NonNull EmergencyNumber emergencyNumber) {
@@ -639,6 +666,7 @@ public class TelephonyRegistryManager {
* subId is invalid.
* @param subId for which radio power state changed.
* @param radioPowerState the current modem radio state.
+ * @hide
*/
public void notifyRadioPowerStateChanged(int slotIndex, int subId,
@RadioPowerState int radioPowerState) {
@@ -654,6 +682,7 @@ public class TelephonyRegistryManager {
* Notify {@link PhoneCapability} changed.
*
* @param phoneCapability the capability of the modem group.
+ * @hide
*/
public void notifyPhoneCapabilityChanged(@NonNull PhoneCapability phoneCapability) {
try {
@@ -685,6 +714,7 @@ public class TelephonyRegistryManager {
* when subId is invalid.
* @param subId for which data activation state changed.
* @param activationState sim activation state e.g, activated.
+ * @hide
*/
public void notifyDataActivationStateChanged(int slotIndex, int subId,
@SimActivationState int activationState) {
@@ -705,6 +735,7 @@ public class TelephonyRegistryManager {
* subId is invalid.
* @param subId for which voice activation state changed.
* @param activationState sim activation state e.g, activated.
+ * @hide
*/
public void notifyVoiceActivationStateChanged(int slotIndex, int subId,
@SimActivationState int activationState) {
@@ -725,6 +756,7 @@ public class TelephonyRegistryManager {
* when subId is invalid.
* @param subId for which mobile data state has changed.
* @param state {@code true} indicates mobile data is enabled/on. {@code false} otherwise.
+ * @hide
*/
public void notifyUserMobileDataStateChanged(int slotIndex, int subId, boolean state) {
try {
@@ -743,6 +775,7 @@ public class TelephonyRegistryManager {
* when the device is in emergency-only mode.
* @param subscriptionId Subscription id for which display network info has changed.
* @param telephonyDisplayInfo The display info.
+ * @hide
*/
public void notifyDisplayInfoChanged(int slotIndex, int subscriptionId,
@NonNull TelephonyDisplayInfo telephonyDisplayInfo) {
@@ -759,6 +792,7 @@ public class TelephonyRegistryManager {
*
* @param subId for which ims call disconnect.
* @param imsReasonInfo the reason for ims call disconnect.
+ * @hide
*/
public void notifyImsDisconnectCause(int subId, @NonNull ImsReasonInfo imsReasonInfo) {
try {
@@ -775,6 +809,7 @@ public class TelephonyRegistryManager {
*
* @param subId for which srvcc state changed.
* @param state srvcc state
+ * @hide
*/
public void notifySrvccStateChanged(int subId, @SrvccState int state) {
try {
@@ -798,6 +833,7 @@ public class TelephonyRegistryManager {
* @param imsServiceTypes Array of IMS call service type for ringing, foreground &
* background calls.
* @param imsCallTypes Array of IMS call type for ringing, foreground & background calls.
+ * @hide
*/
public void notifyPreciseCallState(int slotIndex, int subId,
@Annotation.PreciseCallStates int[] callStates, String[] imsCallIds,
@@ -822,6 +858,7 @@ public class TelephonyRegistryManager {
* @param cause {@link DisconnectCause} for the disconnected call.
* @param preciseCause {@link android.telephony.PreciseDisconnectCause} for the disconnected
* call.
+ * @hide
*/
public void notifyDisconnectCause(int slotIndex, int subId, @DisconnectCauses int cause,
@PreciseDisconnectCauses int preciseCause) {
@@ -838,6 +875,7 @@ public class TelephonyRegistryManager {
*
* <p>To be compatible with {@link TelephonyRegistry}, use {@link CellIdentity} which is
* parcelable, and convert to CellLocation in client code.
+ * @hide
*/
public void notifyCellLocation(int subId, @NonNull CellIdentity cellLocation) {
try {
@@ -854,6 +892,7 @@ public class TelephonyRegistryManager {
*
* @param subId for which cellinfo changed.
* @param cellInfo A list of cellInfo associated with the given subscription.
+ * @hide
*/
public void notifyCellInfoChanged(int subId, @NonNull List<CellInfo> cellInfo) {
try {
@@ -866,6 +905,7 @@ public class TelephonyRegistryManager {
/**
* Notify that the active data subscription ID has changed.
* @param activeDataSubId The new subscription ID for active data
+ * @hide
*/
public void notifyActiveDataSubIdChanged(int activeDataSubId) {
try {
@@ -896,6 +936,7 @@ public class TelephonyRegistryManager {
* For UMTS, if a combined attach succeeds for PS only, then the GMM cause code shall be
* included as an additionalCauseCode. For LTE (ESM), cause codes are in
* TS 24.301 9.9.4.4. Integer.MAX_VALUE if this value is unused.
+ * @hide
*/
public void notifyRegistrationFailed(int slotIndex, int subId,
@NonNull CellIdentity cellIdentity, @NonNull String chosenPlmn,
@@ -914,6 +955,7 @@ public class TelephonyRegistryManager {
* @param slotIndex for the phone object that got updated barring info.
* @param subId for which the BarringInfo changed.
* @param barringInfo updated BarringInfo.
+ * @hide
*/
public void notifyBarringInfoChanged(
int slotIndex, int subId, @NonNull BarringInfo barringInfo) {
@@ -931,6 +973,7 @@ public class TelephonyRegistryManager {
* @param slotIndex for which physical channel configs changed.
* @param subId the subId
* @param configs a list of {@link PhysicalChannelConfig}, the configs of physical channel.
+ * @hide
*/
public void notifyPhysicalChannelConfigForSubscriber(int slotIndex, int subId,
List<PhysicalChannelConfig> configs) {
@@ -948,6 +991,7 @@ public class TelephonyRegistryManager {
* @param enabled True if data is enabled, otherwise disabled.
* @param reason Reason for data enabled/disabled. See {@code REASON_*} in
* {@link TelephonyManager}.
+ * @hide
*/
public void notifyDataEnabled(int slotIndex, int subId, boolean enabled,
@TelephonyManager.DataEnabledReason int reason) {
@@ -966,6 +1010,7 @@ public class TelephonyRegistryManager {
* @param subId for which allowed network types changed.
* @param reason an allowed network type reasons.
* @param allowedNetworkType an allowed network type bitmask value.
+ * @hide
*/
public void notifyAllowedNetworkTypesChanged(int slotIndex, int subId,
int reason, long allowedNetworkType) {
@@ -983,6 +1028,7 @@ public class TelephonyRegistryManager {
* @param slotIndex for the phone object that gets the updated link capacity estimate
* @param subId for subscription that gets the updated link capacity estimate
* @param linkCapacityEstimateList a list of {@link LinkCapacityEstimate}
+ * @hide
*/
public void notifyLinkCapacityEstimateChanged(int slotIndex, int subId,
List<LinkCapacityEstimate> linkCapacityEstimateList) {
@@ -994,6 +1040,29 @@ public class TelephonyRegistryManager {
}
}
+ /**
+ * Notify external listeners that the subscriptions supporting simultaneous cellular calling
+ * have changed.
+ * @param subIds The new set of subIds supporting simultaneous cellular calling.
+ * @hide
+ */
+ public void notifySimultaneousCellularCallingSubscriptionsChanged(
+ @NonNull Set<Integer> subIds) {
+ try {
+ sRegistry.notifySimultaneousCellularCallingSubscriptionsChanged(
+ subIds.stream().mapToInt(i -> i).toArray());
+ } catch (RemoteException ex) {
+ // system server crash
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Processes potential event changes from the provided {@link TelephonyCallback}.
+ *
+ * @param telephonyCallback callback for monitoring callback changes to the telephony state.
+ * @hide
+ */
public @NonNull Set<Integer> getEventsFromCallback(
@NonNull TelephonyCallback telephonyCallback) {
Set<Integer> eventList = new ArraySet<>();
@@ -1135,7 +1204,11 @@ public class TelephonyRegistryManager {
eventList.add(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED);
}
-
+ if (telephonyCallback
+ instanceof TelephonyCallback.SimultaneousCellularCallingSupportListener) {
+ eventList.add(
+ TelephonyCallback.EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED);
+ }
return eventList;
}
@@ -1300,6 +1373,7 @@ public class TelephonyRegistryManager {
* may encounter an {@link IllegalStateException} when trying to register more callbacks.
*
* @param callback The {@link TelephonyCallback} object to register.
+ * @hide
*/
public void registerTelephonyCallback(boolean renounceFineLocationAccess,
boolean renounceCoarseLocationAccess,
@@ -1319,6 +1393,7 @@ public class TelephonyRegistryManager {
* Unregister an existing {@link TelephonyCallback}.
*
* @param callback The {@link TelephonyCallback} object to unregister.
+ * @hide
*/
public void unregisterTelephonyCallback(int subId, String pkgName, String attributionTag,
@NonNull TelephonyCallback callback, boolean notifyNow) {
@@ -1379,6 +1454,7 @@ public class TelephonyRegistryManager {
* @param logicalSlotIndex The SIM slot to listen on
* @param executor The executor where {@code listener} will be invoked
* @param callback The callback to register
+ * @hide
*/
public void addCarrierPrivilegesCallback(
int logicalSlotIndex,
@@ -1413,6 +1489,7 @@ public class TelephonyRegistryManager {
* Unregisters a {@link CarrierPrivilegesCallback}.
*
* @param callback The callback to unregister
+ * @hide
*/
public void removeCarrierPrivilegesCallback(@NonNull CarrierPrivilegesCallback callback) {
if (callback == null) {
@@ -1438,6 +1515,7 @@ public class TelephonyRegistryManager {
* @param logicalSlotIndex The SIM slot the change occurred on
* @param privilegedPackageNames The updated set of packages names with carrier privileges
* @param privilegedUids The updated set of UIDs with carrier privileges
+ * @hide
*/
public void notifyCarrierPrivilegesChanged(
int logicalSlotIndex,
@@ -1463,6 +1541,7 @@ public class TelephonyRegistryManager {
* @param logicalSlotIndex the SIM slot the change occurred on
* @param packageName the package name of the changed {@link CarrierService}
* @param uid the UID of the changed {@link CarrierService}
+ * @hide
*/
public void notifyCarrierServiceChanged(int logicalSlotIndex, @Nullable String packageName,
int uid) {
@@ -1479,6 +1558,7 @@ public class TelephonyRegistryManager {
*
* @param executor The executor on which the callback will be executed.
* @param listener The CarrierConfigChangeListener to be registered with.
+ * @hide
*/
public void addCarrierConfigChangedListener(
@NonNull @CallbackExecutor Executor executor,
@@ -1519,6 +1599,7 @@ public class TelephonyRegistryManager {
* Unregister to stop the notification when carrier configurations changed.
*
* @param listener The CarrierConfigChangeListener to be unregistered with.
+ * @hide
*/
public void removeCarrierConfigChangedListener(
@NonNull CarrierConfigManager.CarrierConfigChangeListener listener) {
@@ -1548,6 +1629,7 @@ public class TelephonyRegistryManager {
* {@link TelephonyManager#UNKNOWN_CARRIER_ID}.
* @param specificCarrierId The optional specific carrier Id, may be {@link
* TelephonyManager#UNKNOWN_CARRIER_ID}.
+ * @hide
*/
public void notifyCarrierConfigChanged(int slotIndex, int subId, int carrierId,
int specificCarrierId) {
@@ -1569,6 +1651,7 @@ public class TelephonyRegistryManager {
* @param subId Sender subscription ID.
* @param type for callback mode entry.
* See {@link TelephonyManager.EmergencyCallbackModeType}.
+ * @hide
*/
public void notifyCallBackModeStarted(int phoneId, int subId,
@TelephonyManager.EmergencyCallbackModeType int type) {
@@ -1589,6 +1672,7 @@ public class TelephonyRegistryManager {
* See {@link TelephonyManager.EmergencyCallbackModeType}.
* @param reason for changing callback mode.
* See {@link TelephonyManager.EmergencyCallbackModeStopReason}.
+ * @hide
*/
public void notifyCallbackModeStopped(int phoneId, int subId,
@TelephonyManager.EmergencyCallbackModeType int type,
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index ac5eb3cbeeaa..b268c2edd9a7 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -671,9 +671,18 @@ public class MeasuredParagraph {
if (mLtrWithoutBidi) {
// If the whole text is LTR direction, just apply whole region.
if (builder == null) {
- mWholeWidth += paint.getTextRunAdvances(
- mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */,
- mWidths.getRawArray(), start);
+ // For the compatibility reasons, the letter spacing should not be dropped at the
+ // left and right edge.
+ int oldFlag = paint.getFlags();
+ paint.setFlags(paint.getFlags()
+ | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE));
+ try {
+ mWholeWidth += paint.getTextRunAdvances(
+ mCopiedBuffer, start, end - start, start, end - start,
+ false /* isRtl */, mWidths.getRawArray(), start);
+ } finally {
+ paint.setFlags(oldFlag);
+ }
} else {
builder.appendStyleRun(paint, config, end - start, false /* isRtl */);
}
@@ -690,9 +699,16 @@ public class MeasuredParagraph {
final boolean isRtl = (level & 0x1) != 0;
if (builder == null) {
final int levelLength = levelEnd - levelStart;
- mWholeWidth += paint.getTextRunAdvances(
- mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
- isRtl, mWidths.getRawArray(), levelStart);
+ int oldFlag = paint.getFlags();
+ paint.setFlags(paint.getFlags()
+ | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE));
+ try {
+ mWholeWidth += paint.getTextRunAdvances(
+ mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
+ isRtl, mWidths.getRawArray(), levelStart);
+ } finally {
+ paint.setFlags(oldFlag);
+ }
} else {
builder.appendStyleRun(paint, config, levelEnd - levelStart, isRtl);
}
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 135935cb0632..2175b47e149e 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -291,6 +291,97 @@ public class TextLine {
}
/**
+ * Returns the run flag of at the given BiDi run.
+ *
+ * @param bidiRunIndex a BiDi run index.
+ * @return a run flag of the given BiDi run.
+ */
+ @VisibleForTesting
+ public static int calculateRunFlag(int bidiRunIndex, int bidiRunCount, int lineDirection) {
+ if (bidiRunCount == 1) {
+ // Easy case. If there is only single run, it is most left and most right run.
+ return Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+ }
+ if (bidiRunIndex != 0 && bidiRunIndex != (bidiRunCount - 1)) {
+ // Easy case. If the given run is the middle of the line, it is not the most left or
+ // the most right run.
+ return 0;
+ }
+
+ int runFlag = 0;
+ // For the historical reasons, the BiDi implementation of Android works differently
+ // from the Java BiDi APIs. The mDirections holds the BiDi runs in visual order, but
+ // it is reversed order if the paragraph direction is RTL. So, the first BiDi run of
+ // mDirections is located the most left of the line if the paragraph direction is LTR.
+ // If the paragraph direction is RTL, the first BiDi run is located the most right of
+ // the line.
+ if (bidiRunIndex == 0) {
+ if (lineDirection == Layout.DIR_LEFT_TO_RIGHT) {
+ runFlag |= Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+ } else {
+ runFlag |= Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+ }
+ }
+ if (bidiRunIndex == (bidiRunCount - 1)) {
+ if (lineDirection == Layout.DIR_LEFT_TO_RIGHT) {
+ runFlag |= Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+ } else {
+ runFlag |= Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+ }
+ }
+ return runFlag;
+ }
+
+ /**
+ * Resolve the runFlag for the inline span range.
+ *
+ * @param runFlag the runFlag of the current BiDi run.
+ * @param isRtlRun true for RTL run, false for LTR run.
+ * @param runStart the inclusive BiDi run start offset.
+ * @param runEnd the exclusive BiDi run end offset.
+ * @param spanStart the inclusive span start offset.
+ * @param spanEnd the exclusive span end offset.
+ * @return the resolved runFlag.
+ */
+ @VisibleForTesting
+ public static int resolveRunFlagForSubSequence(int runFlag, boolean isRtlRun, int runStart,
+ int runEnd, int spanStart, int spanEnd) {
+ if (runFlag == 0) {
+ // Easy case. If the run is in the middle of the line, any inline span is also in the
+ // middle of the line.
+ return 0;
+ }
+ int localRunFlag = runFlag;
+ if ((runFlag & Paint.TEXT_RUN_FLAG_LEFT_EDGE) != 0) {
+ if (isRtlRun) {
+ if (spanEnd != runEnd) {
+ // In the RTL context, the last run is the most left run.
+ localRunFlag &= ~Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+ }
+ } else { // LTR
+ if (spanStart != runStart) {
+ // In the LTR context, the first run is the most left run.
+ localRunFlag &= ~Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+ }
+ }
+ }
+ if ((runFlag & Paint.TEXT_RUN_FLAG_RIGHT_EDGE) != 0) {
+ if (isRtlRun) {
+ if (spanStart != runStart) {
+ // In the RTL context, the start of the run is the most right run.
+ localRunFlag &= ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+ }
+ } else { // LTR
+ if (spanEnd != runEnd) {
+ // In the LTR context, the last run is the most right position.
+ localRunFlag &= ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+ }
+ }
+ }
+ return localRunFlag;
+ }
+
+ /**
* Renders the TextLine.
*
* @param c the canvas to render on
@@ -308,11 +399,13 @@ public class TextLine {
final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
+
int segStart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
if (j == runLimit || charAt(j) == TAB_CHAR) {
h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
- runIndex != (runCount - 1) || j != mLen);
+ runIndex != (runCount - 1) || j != mLen, runFlag);
if (j != runLimit) { // charAt(j) == TAB_CHAR
h = mDir * nextTab(h * mDir);
@@ -371,11 +464,12 @@ public class TextLine {
final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
int segStart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
if (j == runLimit || charAt(j) == TAB_CHAR) {
horizontal += shapeRun(consumer, segStart, j, runIsRtl, x + horizontal,
- runIndex != (runCount - 1) || j != mLen);
+ runIndex != (runCount - 1) || j != mLen, runFlag);
if (j != runLimit) { // charAt(j) == TAB_CHAR
horizontal = mDir * nextTab(horizontal * mDir);
@@ -441,11 +535,13 @@ public class TextLine {
}
float h = 0;
- for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+ final int runCount = mDirections.getRunCount();
+ for (int runIndex = 0; runIndex < runCount; runIndex++) {
final int runStart = mDirections.getRunStart(runIndex);
if (runStart > mLen) break;
final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
int segStart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
@@ -455,16 +551,16 @@ public class TextLine {
if (targetIsInThisSegment && sameDirection) {
return h + measureRun(segStart, offset, j, runIsRtl, fmi, drawBounds, null,
- 0, h, lineInfo);
+ 0, h, lineInfo, runFlag);
}
final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi, drawBounds,
- null, 0, h, lineInfo);
+ null, 0, h, lineInfo, runFlag);
h += sameDirection ? segmentWidth : -segmentWidth;
if (targetIsInThisSegment) {
return h + measureRun(segStart, offset, j, runIsRtl, null, null, null, 0,
- h, lineInfo);
+ h, lineInfo, runFlag);
}
if (j != runLimit) { // charAt(j) == TAB_CHAR
@@ -543,20 +639,21 @@ public class TextLine {
+ "result, needed: " + mLen + " had: " + advances.length);
}
float h = 0;
- for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+ final int runCount = mDirections.getRunCount();
+ for (int runIndex = 0; runIndex < runCount; runIndex++) {
final int runStart = mDirections.getRunStart(runIndex);
if (runStart > mLen) break;
final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
int segStart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
if (j == runLimit || charAt(j) == TAB_CHAR) {
final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
-
final float segmentWidth =
measureRun(segStart, j, j, runIsRtl, null, null, advances, segStart, 0,
- null);
+ null, runFlag);
final float oldh = h;
h += sameDirection ? segmentWidth : -segmentWidth;
@@ -608,11 +705,13 @@ public class TextLine {
}
float horizontal = 0;
- for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+ final int runCount = mDirections.getRunCount();
+ for (int runIndex = 0; runIndex < runCount; runIndex++) {
final int runStart = mDirections.getRunStart(runIndex);
if (runStart > mLen) break;
final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+ final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
int segStart = runStart;
for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; ++j) {
@@ -629,7 +728,7 @@ public class TextLine {
final float previousSegEndHorizontal = measurement[segStart];
final float width =
measureRun(segStart, j, j, runIsRtl, fmi, null, measurement, segStart,
- 0, null);
+ 0, null, runFlag);
horizontal += sameDirection ? width : -width;
float currHorizontal = sameDirection ? oldHorizontal : horizontal;
@@ -686,22 +785,24 @@ public class TextLine {
* @param y the baseline
* @param bottom the bottom of the line
* @param needWidth true if the width value is required.
+ * @param runFlag the run flag to be applied for this run.
* @return the signed width of the run, based on the paragraph direction.
* Only valid if needWidth is true.
*/
private float drawRun(Canvas c, int start,
int limit, boolean runIsRtl, float x, int top, int y, int bottom,
- boolean needWidth) {
+ boolean needWidth, int runFlag) {
if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
- float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null);
+ float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null,
+ runFlag);
handleRun(start, limit, limit, runIsRtl, c, null, x + w, top,
- y, bottom, null, null, false, null, 0, null);
+ y, bottom, null, null, false, null, 0, null, runFlag);
return w;
}
return handleRun(start, limit, limit, runIsRtl, c, null, x, top,
- y, bottom, null, null, needWidth, null, 0, null);
+ y, bottom, null, null, needWidth, null, 0, null, runFlag);
}
/**
@@ -718,19 +819,21 @@ public class TextLine {
* @param advancesIndex the start index to fill in the advance information.
* @param x horizontal offset of the run.
* @param lineInfo an optional output parameter for filling line information.
+ * @param runFlag the run flag to be applied for this run.
* @return the signed width from the start of the run to the leading edge
* of the character at offset, based on the run (not paragraph) direction
*/
private float measureRun(int start, int offset, int limit, boolean runIsRtl,
@Nullable FontMetricsInt fmi, @Nullable RectF drawBounds, @Nullable float[] advances,
- int advancesIndex, float x, @Nullable LineInfo lineInfo) {
+ int advancesIndex, float x, @Nullable LineInfo lineInfo, int runFlag) {
if (drawBounds != null && (mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
- float w = -measureRun(start, offset, limit, runIsRtl, null, null, null, 0, 0, null);
+ float w = -measureRun(start, offset, limit, runIsRtl, null, null, null, 0, 0, null,
+ runFlag);
return handleRun(start, offset, limit, runIsRtl, null, null, x + w, 0, 0, 0, fmi,
- drawBounds, true, advances, advancesIndex, lineInfo);
+ drawBounds, true, advances, advancesIndex, lineInfo, runFlag);
}
return handleRun(start, offset, limit, runIsRtl, null, null, x, 0, 0, 0, fmi, drawBounds,
- true, advances, advancesIndex, lineInfo);
+ true, advances, advancesIndex, lineInfo, runFlag);
}
/**
@@ -742,21 +845,23 @@ public class TextLine {
* @param runIsRtl true if the run is right-to-left
* @param x the position of the run that is closest to the leading margin
* @param needWidth true if the width value is required.
+ * @param runFlag the run flag to be applied for this run.
* @return the signed width of the run, based on the paragraph direction.
* Only valid if needWidth is true.
*/
private float shapeRun(TextShaper.GlyphsConsumer consumer, int start,
- int limit, boolean runIsRtl, float x, boolean needWidth) {
+ int limit, boolean runIsRtl, float x, boolean needWidth, int runFlag) {
if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
- float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null);
+ float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null,
+ runFlag);
handleRun(start, limit, limit, runIsRtl, null, consumer, x + w, 0, 0, 0, null, null,
- false, null, 0, null);
+ false, null, 0, null, runFlag);
return w;
}
return handleRun(start, limit, limit, runIsRtl, null, consumer, x, 0, 0, 0, null, null,
- needWidth, null, 0, null);
+ needWidth, null, 0, null, runFlag);
}
@@ -1160,6 +1265,7 @@ public class TextLine {
* @param advances receives the advance information about the requested run, can be null.
* @param advancesIndex the start index to fill in the advance information.
* @param lineInfo an optional output parameter for filling line information.
+ * @param runFlag the run flag to be applied for this run.
* @return the signed width of the run based on the run direction; only
* valid if needWidth is true
*/
@@ -1168,8 +1274,8 @@ public class TextLine {
Canvas c, TextShaper.GlyphsConsumer consumer, float x, int top, int y, int bottom,
FontMetricsInt fmi, RectF drawBounds, boolean needWidth, int offset,
@Nullable ArrayList<DecorationInfo> decorations,
- @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo) {
-
+ @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo,
+ int runFlag) {
if (mIsJustifying) {
wp.setWordSpacing(mAddedWidthForJustify);
}
@@ -1187,7 +1293,16 @@ public class TextLine {
}
float totalWidth = 0;
-
+ if ((runFlag & Paint.TEXT_RUN_FLAG_LEFT_EDGE) == Paint.TEXT_RUN_FLAG_LEFT_EDGE) {
+ wp.setFlags(wp.getFlags() | Paint.TEXT_RUN_FLAG_LEFT_EDGE);
+ } else {
+ wp.setFlags(wp.getFlags() & ~Paint.TEXT_RUN_FLAG_LEFT_EDGE);
+ }
+ if ((runFlag & Paint.TEXT_RUN_FLAG_RIGHT_EDGE) == Paint.TEXT_RUN_FLAG_RIGHT_EDGE) {
+ wp.setFlags(wp.getFlags() | Paint.TEXT_RUN_FLAG_RIGHT_EDGE);
+ } else {
+ wp.setFlags(wp.getFlags() & ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE);
+ }
final int numDecorations = decorations == null ? 0 : decorations.size();
if (needWidth || ((c != null || consumer != null) && (wp.bgColor != 0
|| numDecorations != 0 || runIsRtl))) {
@@ -1419,6 +1534,7 @@ public class TextLine {
* @param advances receives the advance information about the requested run, can be null.
* @param advancesIndex the start index to fill in the advance information.
* @param lineInfo an optional output parameter for filling line information.
+ * @param runFlag the run flag to be applied for this run.
* @return the signed width of the run based on the run direction; only
* valid if needWidth is true
*/
@@ -1426,7 +1542,8 @@ public class TextLine {
int limit, boolean runIsRtl, Canvas c,
TextShaper.GlyphsConsumer consumer, float x, int top, int y,
int bottom, FontMetricsInt fmi, RectF drawBounds, boolean needWidth,
- @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo) {
+ @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo,
+ int runFlag) {
if (measureLimit < start || measureLimit > limit) {
throw new IndexOutOfBoundsException("measureLimit (" + measureLimit + ") is out of "
@@ -1473,7 +1590,7 @@ public class TextLine {
wp.setEndHyphenEdit(adjustEndHyphenEdit(limit, wp.getEndHyphenEdit()));
return handleText(wp, start, limit, start, limit, runIsRtl, c, consumer, x, top,
y, bottom, fmi, drawBounds, needWidth, measureLimit, null, advances,
- advancesIndex, lineInfo);
+ advancesIndex, lineInfo, runFlag);
}
// Shaping needs to take into account context up to metric boundaries,
@@ -1554,6 +1671,9 @@ public class TextLine {
// and use.
activePaint.set(wp);
} else if (!equalAttributes(wp, activePaint)) {
+ final int spanRunFlag = resolveRunFlagForSubSequence(
+ runFlag, runIsRtl, start, measureLimit, activeStart, activeEnd);
+
// The style of the present chunk of text is substantially different from the
// style of the previous chunk. We need to handle the active piece of text
// and restart with the present chunk.
@@ -1565,7 +1685,7 @@ public class TextLine {
consumer, x, top, y, bottom, fmi, drawBounds,
needWidth || activeEnd < measureLimit,
Math.min(activeEnd, mlimit), mDecorations,
- advances, advancesIndex + activeStart - start, lineInfo);
+ advances, advancesIndex + activeStart - start, lineInfo, spanRunFlag);
activeStart = j;
activePaint.set(wp);
@@ -1585,6 +1705,9 @@ public class TextLine {
mDecorations.add(copy);
}
}
+
+ final int spanRunFlag = resolveRunFlagForSubSequence(
+ runFlag, runIsRtl, start, measureLimit, activeStart, activeEnd);
// Handle the final piece of text.
activePaint.setStartHyphenEdit(
adjustStartHyphenEdit(activeStart, mPaint.getStartHyphenEdit()));
@@ -1593,7 +1716,7 @@ public class TextLine {
x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, consumer, x,
top, y, bottom, fmi, drawBounds, needWidth || activeEnd < measureLimit,
Math.min(activeEnd, mlimit), mDecorations,
- advances, advancesIndex + activeStart - start, lineInfo);
+ advances, advancesIndex + activeStart - start, lineInfo, spanRunFlag);
}
return x - originalX;
@@ -1614,7 +1737,6 @@ public class TextLine {
*/
private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
-
if (mCharsValid) {
int count = end - start;
int contextCount = contextEnd - contextStart;
diff --git a/core/java/android/tracing/transition/TransitionDataSource.java b/core/java/android/tracing/transition/TransitionDataSource.java
index b8daace30d0a..baece75cbf09 100644
--- a/core/java/android/tracing/transition/TransitionDataSource.java
+++ b/core/java/android/tracing/transition/TransitionDataSource.java
@@ -16,6 +16,7 @@
package android.tracing.transition;
+import android.tracing.perfetto.CreateTlsStateArgs;
import android.tracing.perfetto.DataSource;
import android.tracing.perfetto.DataSourceInstance;
import android.tracing.perfetto.FlushCallbackArguments;
@@ -23,10 +24,14 @@ import android.tracing.perfetto.StartCallbackArguments;
import android.tracing.perfetto.StopCallbackArguments;
import android.util.proto.ProtoInputStream;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* @hide
*/
-public class TransitionDataSource extends DataSource {
+public class TransitionDataSource
+ extends DataSource<DataSourceInstance, TransitionDataSource.TlsState, Void> {
public static String DATA_SOURCE_NAME = "com.android.wm.shell.transition";
private final Runnable mOnStartStaticCallback;
@@ -41,6 +46,15 @@ public class TransitionDataSource extends DataSource {
}
@Override
+ protected TlsState createTlsState(CreateTlsStateArgs<DataSourceInstance> args) {
+ return new TlsState();
+ }
+
+ public class TlsState {
+ public final Map<String, Integer> handlerMapping = new HashMap<>();
+ }
+
+ @Override
public DataSourceInstance createInstance(ProtoInputStream configStream, int instanceIndex) {
return new DataSourceInstance(this, instanceIndex) {
@Override
diff --git a/core/java/android/view/EventLogTags.logtags b/core/java/android/view/EventLogTags.logtags
index cc0b18a652d5..f1cd671ef176 100644
--- a/core/java/android/view/EventLogTags.logtags
+++ b/core/java/android/view/EventLogTags.logtags
@@ -59,5 +59,10 @@ option java_package android.view
# Enqueue Input Event
62002 view_enqueue_input_event (eventType|3),(action|3)
+# following other view events defined in system/logging/logcat/event.logtags
+# ViewRoot Draw Events
+60004 viewroot_draw_event (window|3),(event|3)
+
+
# NOTE - the range 1000000-2000000 is reserved for partners and others who
# want to define their own log tags without conflicting with the core platform. \ No newline at end of file
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 36b74e39072a..7903050e41d4 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -69,12 +69,13 @@ import android.view.SurfaceControl;
import android.view.displayhash.DisplayHash;
import android.view.displayhash.VerifiedDisplayHash;
import android.window.AddToSurfaceSyncGroupResult;
+import android.window.IScreenRecordingCallback;
import android.window.ISurfaceSyncGroupCompletedListener;
import android.window.ITaskFpsCallback;
-import android.window.ScreenCapture;
-import android.window.WindowContextInfo;
import android.window.ITrustedPresentationListener;
+import android.window.ScreenCapture;
import android.window.TrustedPresentationThresholds;
+import android.window.WindowContextInfo;
/**
* System private interface to the window manager.
@@ -1083,4 +1084,8 @@ interface IWindowManager
void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id);
+
+ boolean registerScreenRecordingCallback(IScreenRecordingCallback callback);
+
+ void unregisterScreenRecordingCallback(IScreenRecordingCallback callback);
}
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index 785055441d59..ba7874eb2d21 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -39,8 +39,10 @@ import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
+import android.view.flags.Flags;
import dalvik.system.CloseGuard;
+import dalvik.system.VMRuntime;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -101,6 +103,10 @@ public class Surface implements Parcelable {
long nativeObject, float frameRate, int compatibility, int changeFrameRateStrategy);
private static native void nativeDestroy(long nativeObject);
+ // 5MB is a wild guess for what the average surface should be. On most new phones, a full-screen
+ // surface is about 9MB... but not all surfaces are screen size. This should be a nice balance.
+ private static final long SURFACE_NATIVE_ALLOCATION_SIZE_BYTES = 5_000_000;
+
public static final @android.annotation.NonNull Parcelable.Creator<Surface> CREATOR =
new Parcelable.Creator<Surface>() {
@Override
@@ -331,6 +337,7 @@ public class Surface implements Parcelable {
*/
@UnsupportedAppUsage
public Surface() {
+ registerNativeMemoryUsage();
}
/**
@@ -343,6 +350,7 @@ public class Surface implements Parcelable {
*/
public Surface(@NonNull SurfaceControl from) {
copyFrom(from);
+ registerNativeMemoryUsage();
}
/**
@@ -370,6 +378,7 @@ public class Surface implements Parcelable {
mName = surfaceTexture.toString();
setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture));
}
+ registerNativeMemoryUsage();
}
/* called from android_view_Surface_createFromIGraphicBufferProducer() */
@@ -378,6 +387,7 @@ public class Surface implements Parcelable {
synchronized (mLock) {
setNativeObjectLocked(nativeObject);
}
+ registerNativeMemoryUsage();
}
@Override
@@ -389,6 +399,7 @@ public class Surface implements Parcelable {
release();
} finally {
super.finalize();
+ freeNativeMemoryUsage();
}
}
@@ -1243,4 +1254,16 @@ public class Surface implements Parcelable {
return mIsWideColorGamut;
}
}
+
+ private static void registerNativeMemoryUsage() {
+ if (Flags.enableSurfaceNativeAllocRegistration()) {
+ VMRuntime.getRuntime().registerNativeAllocation(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES);
+ }
+ }
+
+ private static void freeNativeMemoryUsage() {
+ if (Flags.enableSurfaceNativeAllocRegistration()) {
+ VMRuntime.getRuntime().registerNativeFree(SURFACE_NATIVE_ALLOCATION_SIZE_BYTES);
+ }
+ }
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 674f22c4166e..3ed03859ffa6 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -294,8 +294,8 @@ public final class SurfaceControl implements Parcelable {
private static native void nativeClearTrustedPresentationCallback(long transactionObj,
long nativeObject);
private static native StalledTransactionInfo nativeGetStalledTransactionInfo(int pid);
- private static native void nativeSetDesiredPresentTime(long transactionObj,
- long desiredPresentTime);
+ private static native void nativeSetDesiredPresentTimeNanos(long transactionObj,
+ long desiredPresentTimeNanos);
private static native void nativeSetFrameTimeline(long transactionObj,
long vsyncId);
@@ -2563,12 +2563,12 @@ public final class SurfaceControl implements Parcelable {
*/
@FlaggedApi(Flags.FLAG_SDK_DESIRED_PRESENT_TIME)
public static final class TransactionStats {
- private long mLatchTime;
+ private long mLatchTimeNanos;
private SyncFence mSyncFence;
// called from native
- private TransactionStats(long latchTime, long presentFencePtr) {
- mLatchTime = latchTime;
+ private TransactionStats(long latchTimeNanos, long presentFencePtr) {
+ mLatchTimeNanos = latchTimeNanos;
mSyncFence = new SyncFence(presentFencePtr);
}
@@ -2581,12 +2581,12 @@ public final class SurfaceControl implements Parcelable {
}
/**
- * Returns the timestamp of when the frame was latched by the framework and queued for
- * presentation.
+ * Returns the timestamp (in CLOCK_MONOTONIC) of when the frame was latched by the
+ * framework and queued for presentation.
*/
@FlaggedApi(Flags.FLAG_SDK_DESIRED_PRESENT_TIME)
- public long getLatchTime() {
- return mLatchTime;
+ public long getLatchTimeNanos() {
+ return mLatchTimeNanos;
}
/**
@@ -4426,8 +4426,8 @@ public final class SurfaceControl implements Parcelable {
}
/**
- * Specifies a desiredPresentTime for the transaction. The framework will try to present
- * the transaction at or after the time specified.
+ * Specifies a desiredPresentTimeNanos for the transaction. The framework will try to
+ * present the transaction at or after the time specified.
*
* Transactions will not be presented until all of their acquire fences have signaled even
* if the app requests an earlier present time.
@@ -4436,17 +4436,17 @@ public final class SurfaceControl implements Parcelable {
* a desired present time that is before x, the later transaction will not preempt the
* earlier transaction.
*
- * @param desiredPresentTime The desired time (in CLOCK_MONOTONIC) for the transaction.
+ * @param desiredPresentTimeNanos The desired time (in CLOCK_MONOTONIC) for the transaction.
* @return This transaction
*/
@FlaggedApi(Flags.FLAG_SDK_DESIRED_PRESENT_TIME)
@NonNull
- public Transaction setDesiredPresentTime(long desiredPresentTime) {
+ public Transaction setDesiredPresentTimeNanos(long desiredPresentTimeNanos) {
if (!Flags.sdkDesiredPresentTime()) {
Log.w(TAG, "addTransactionCompletedListener was called but flag is disabled");
return this;
}
- nativeSetDesiredPresentTime(mNativeObject, desiredPresentTime);
+ nativeSetDesiredPresentTimeNanos(mNativeObject, desiredPresentTimeNanos);
return this;
}
/**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 257ecc565c87..c98d1d7ecaea 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -15015,6 +15015,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
}
/** @hide */
+ @Nullable
View getSelfOrParentImportantForA11y() {
if (isImportantForAccessibility()) return this;
ViewParent parent = getParentForAccessibility();
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index c66f3c8032fd..e0bda9181380 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -26,6 +26,7 @@ import static android.view.Display.INVALID_DISPLAY;
import static android.view.InputDevice.SOURCE_CLASS_NONE;
import static android.view.InsetsSource.ID_IME;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
import static android.view.View.PFLAG_DRAW_ANIMATION;
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
@@ -87,6 +88,7 @@ import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_B
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
+import static android.view.accessibility.Flags.fixMergedContentChangeEvent;
import static android.view.accessibility.Flags.forceInvertColor;
import static android.view.accessibility.Flags.reduceWindowContentChangedEventThrottle;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
@@ -285,6 +287,7 @@ public final class ViewRootImpl implements ViewParent,
private static final boolean DEBUG_TOUCH_NAVIGATION = false || LOCAL_LOGV;
private static final boolean DEBUG_BLAST = false || LOCAL_LOGV;
private static final int LOGTAG_INPUT_FOCUS = 62001;
+ private static final int LOGTAG_VIEWROOT_DRAW_EVENT = 60004;
/**
* Set to false if we do not want to use the multi threaded renderer even though
@@ -1009,8 +1012,10 @@ public final class ViewRootImpl implements ViewParent,
// Used to check if there were any view invalidations in
// the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME).
private boolean mHasInvalidation = false;
- // Used to check if it is in the touch boosting period.
+ // Used to check if it is in the frame rate boosting period.
private boolean mIsFrameRateBoosting = false;
+ // Used to check if it is in touch boosting period.
+ private boolean mIsTouchBoosting = false;
// Used to check if there is a message in the message queue
// for idleness handling.
private boolean mHasIdledMessage = false;
@@ -4749,13 +4754,7 @@ public final class ViewRootImpl implements ViewParent,
}
private void reportDrawFinished(@Nullable Transaction t, int seqId) {
- if (DEBUG_BLAST) {
- Log.d(mTag, "reportDrawFinished");
- }
-
- if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
- Trace.instant(Trace.TRACE_TAG_VIEW, "reportDrawFinished " + mTag + " seqId=" + seqId);
- }
+ logAndTrace("reportDrawFinished seqId=" + seqId);
try {
mWindowSession.finishDrawing(mWindow, t, seqId);
} catch (RemoteException e) {
@@ -6425,11 +6424,12 @@ public final class ViewRootImpl implements ViewParent,
* Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME).
*/
mIsFrameRateBoosting = false;
+ mIsTouchBoosting = false;
setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory,
mLastPreferredFrameRateCategory));
break;
case MSG_CHECK_INVALIDATION_IDLE:
- if (!mHasInvalidation && !mIsFrameRateBoosting) {
+ if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) {
mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
mHasIdledMessage = false;
@@ -7452,7 +7452,7 @@ public final class ViewRootImpl implements ViewParent,
// For the variable refresh rate project
if (handled && shouldTouchBoost(action, mWindowAttributes.type)) {
// set the frame rate to the maximum value.
- mIsFrameRateBoosting = true;
+ mIsTouchBoosting = true;
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
}
/**
@@ -11509,6 +11509,15 @@ public final class ViewRootImpl implements ViewParent,
event.setContentChangeTypes(mChangeTypes);
if (mAction.isPresent()) event.setAction(mAction.getAsInt());
if (AccessibilityEvent.DEBUG_ORIGIN) event.originStackTrace = mOrigin;
+
+ if (fixMergedContentChangeEvent()) {
+ if ((mChangeTypes & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) {
+ final View importantParent = source.getSelfOrParentImportantForA11y();
+ if (importantParent != null) {
+ source = importantParent;
+ }
+ }
+ }
source.sendAccessibilityEventUnchecked(event);
} else {
mLastEventTimeMillis = 0;
@@ -11543,14 +11552,29 @@ public final class ViewRootImpl implements ViewParent,
}
if (mSource != null) {
- // If there is no common predecessor, then mSource points to
- // a removed view, hence in this case always prefer the source.
- View predecessor = getCommonPredecessor(mSource, source);
- if (predecessor != null) {
- predecessor = predecessor.getSelfOrParentImportantForA11y();
+ if (fixMergedContentChangeEvent()) {
+ View newSource = getCommonPredecessor(mSource, source);
+ if (newSource == null) {
+ // If there is no common predecessor, then mSource points to
+ // a removed view, hence in this case always prefer the source.
+ newSource = source;
+ }
+
+ mChangeTypes |= changeType;
+ if (mSource != newSource) {
+ mChangeTypes |= AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
+ mSource = newSource;
+ }
+ } else {
+ // If there is no common predecessor, then mSource points to
+ // a removed view, hence in this case always prefer the source.
+ View predecessor = getCommonPredecessor(mSource, source);
+ if (predecessor != null) {
+ predecessor = predecessor.getSelfOrParentImportantForA11y();
+ }
+ mSource = (predecessor != null) ? predecessor : source;
+ mChangeTypes |= changeType;
}
- mSource = (predecessor != null) ? predecessor : source;
- mChangeTypes |= changeType;
final int performingAction = mAccessibilityManager.getPerformingAction();
if (performingAction != 0) {
@@ -12169,7 +12193,10 @@ public final class ViewRootImpl implements ViewParent,
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.instant(Trace.TRACE_TAG_VIEW, mTag + "-" + msg);
}
- Log.d(mTag, msg);
+ if (DEBUG_BLAST) {
+ Log.d(mTag, msg);
+ }
+ EventLog.writeEvent(LOGTAG_VIEWROOT_DRAW_EVENT, mTag, msg);
}
private void setPreferredFrameRateCategory(int preferredFrameRateCategory) {
@@ -12177,8 +12204,16 @@ public final class ViewRootImpl implements ViewParent,
return;
}
- int frameRateCategory = mIsFrameRateBoosting || mInsetsAnimationRunning
- ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory;
+ int frameRateCategory = mIsTouchBoosting
+ ? FRAME_RATE_CATEGORY_HIGH_HINT : preferredFrameRateCategory;
+
+ // FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT
+ // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction.
+ // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction
+ // (e.g., Window Initialization).
+ if (mIsFrameRateBoosting || mInsetsAnimationRunning) {
+ frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
+ }
try {
if (mLastPreferredFrameRateCategory != frameRateCategory) {
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index c7355c144c5f..efae57c9946c 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -53,6 +53,13 @@ flag {
flag {
namespace: "accessibility"
+ name: "fix_merged_content_change_event"
+ description: "Fixes event type and source of content change event merged in ViewRootImpl"
+ bug: "277305460"
+}
+
+flag {
+ namespace: "accessibility"
name: "flash_notification_system_api"
description: "Makes flash notification APIs as system APIs for calling from mainline module"
bug: "303131332"
diff --git a/core/java/android/window/IScreenRecordingCallback.aidl b/core/java/android/window/IScreenRecordingCallback.aidl
new file mode 100644
index 000000000000..560ee75aa365
--- /dev/null
+++ b/core/java/android/window/IScreenRecordingCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.window;
+
+/**
+ * @hide
+ */
+oneway interface IScreenRecordingCallback {
+ void onScreenRecordingStateChanged(boolean visibleInScreenRecording);
+}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index bceb8726b1cb..feae173f3e61 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -421,8 +421,11 @@ public final class TransitionInfo implements Parcelable {
final String perChangeLineStart = shouldPrettyPrint ? "\n" + innerPrefix : "";
StringBuilder sb = new StringBuilder();
sb.append("{id=").append(mDebugId).append(" t=").append(transitTypeToString(mType))
- .append(" f=0x").append(Integer.toHexString(mFlags)).append(" trk=").append(mTrack)
- .append(" r=[");
+ .append(" f=0x").append(Integer.toHexString(mFlags)).append(" trk=").append(mTrack);
+ if (mOptions != null) {
+ sb.append(" opt=").append(mOptions);
+ }
+ sb.append(" r=[");
for (int i = 0; i < mRoots.size(); ++i) {
if (i > 0) {
sb.append(',');
@@ -1211,21 +1214,31 @@ public final class TransitionInfo implements Parcelable {
@NonNull
private static String typeToString(int mode) {
- switch(mode) {
- case ANIM_CUSTOM: return "ANIM_CUSTOM";
- case ANIM_CLIP_REVEAL: return "ANIM_CLIP_REVEAL";
- case ANIM_SCALE_UP: return "ANIM_SCALE_UP";
- case ANIM_THUMBNAIL_SCALE_UP: return "ANIM_THUMBNAIL_SCALE_UP";
- case ANIM_THUMBNAIL_SCALE_DOWN: return "ANIM_THUMBNAIL_SCALE_DOWN";
- case ANIM_OPEN_CROSS_PROFILE_APPS: return "ANIM_OPEN_CROSS_PROFILE_APPS";
- default: return "<unknown:" + mode + ">";
- }
+ return switch (mode) {
+ case ANIM_CUSTOM -> "CUSTOM";
+ case ANIM_SCALE_UP -> "SCALE_UP";
+ case ANIM_THUMBNAIL_SCALE_UP -> "THUMBNAIL_SCALE_UP";
+ case ANIM_THUMBNAIL_SCALE_DOWN -> "THUMBNAIL_SCALE_DOWN";
+ case ANIM_SCENE_TRANSITION -> "SCENE_TRANSITION";
+ case ANIM_CLIP_REVEAL -> "CLIP_REVEAL";
+ case ANIM_OPEN_CROSS_PROFILE_APPS -> "OPEN_CROSS_PROFILE_APPS";
+ case ANIM_FROM_STYLE -> "FROM_STYLE";
+ default -> "<" + mode + ">";
+ };
}
@Override
public String toString() {
- return "{ AnimationOptions type= " + typeToString(mType) + " package=" + mPackageName
- + " override=" + mOverrideTaskTransition + " b=" + mTransitionBounds + "}";
+ final StringBuilder sb = new StringBuilder(32);
+ sb.append("{t=").append(typeToString(mType));
+ if (mOverrideTaskTransition) {
+ sb.append(" overrideTask=true");
+ }
+ if (!mTransitionBounds.isEmpty()) {
+ sb.append(" bounds=").append(mTransitionBounds);
+ }
+ sb.append('}');
+ return sb.toString();
}
/** Customized activity transition. */
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index ad0e9a487c53..f67eefa2281f 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -8,6 +8,13 @@ flag {
}
flag {
+ name: "bal_require_opt_in_same_uid"
+ namespace: "responsible_apis"
+ description: "Require the PendingIntent creator/sender to opt in if it is the same UID"
+ bug: "296478951"
+}
+
+flag {
name: "bal_dont_bring_existing_background_task_stack_to_fg"
namespace: "responsible_apis"
description: "When starting a PendingIntent with ONLY creator privileges, don't bring the existing task stack to foreground"
diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig
index 3c3c8469b3a4..751c1a8dd0ff 100644
--- a/core/java/android/window/flags/window_surfaces.aconfig
+++ b/core/java/android/window/flags/window_surfaces.aconfig
@@ -80,3 +80,11 @@ flag {
is_fixed_read_only: true
bug: "278757236"
}
+
+flag {
+ namespace: "window_surfaces"
+ name: "screen_recording_callbacks"
+ description: "Enable screen recording callbacks public API"
+ is_fixed_read_only: true
+ bug: "304574518"
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index bb16ad2cb8de..2c5fbd7f3725 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -69,11 +69,10 @@ flag {
}
flag {
- name: "predictive_back_system_animations"
+ name: "predictive_back_system_anims"
namespace: "systemui"
description: "Predictive back for system animations"
- bug: "319421778"
- is_fixed_read_only: true
+ bug: "320510464"
}
flag {
@@ -90,4 +89,12 @@ flag {
description: "Feature flag to enable a multi-instance system ui component property."
bug: "262864589"
is_fixed_read_only: true
+}
+
+flag {
+ name: "insets_decoupled_configuration"
+ namespace: "windowing_frontend"
+ description: "Configuration decoupled from insets"
+ bug: "151861875"
+ is_fixed_read_only: true
} \ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index f743ab74d1f5..6e5807b21d3d 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -46,4 +46,11 @@ flag {
description: "To dispatch ActivityWindowInfo through ClientTransaction"
bug: "287582673"
is_fixed_read_only: true
+}
+
+flag {
+ namespace: "windowing_sdk"
+ name: "embedded_activity_back_nav_flag"
+ description: "Refines embedded activity back navigation behavior"
+ bug: "240575809"
} \ No newline at end of file
diff --git a/core/java/com/android/internal/os/MonotonicClock.java b/core/java/com/android/internal/os/MonotonicClock.java
index c3bcfa6b2cc2..eca6f58dd50f 100644
--- a/core/java/com/android/internal/os/MonotonicClock.java
+++ b/core/java/com/android/internal/os/MonotonicClock.java
@@ -17,11 +17,11 @@
package com.android.internal.os;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Xml;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
@@ -49,20 +49,27 @@ public class MonotonicClock {
private final AtomicFile mFile;
private final Clock mClock;
- private long mTimeshift;
+ private final long mTimeshift;
public static final long UNDEFINED = -1;
public MonotonicClock(File file) {
- mFile = new AtomicFile(file);
- mClock = Clock.SYSTEM_CLOCK;
- read();
+ this (file, Clock.SYSTEM_CLOCK.elapsedRealtime(), Clock.SYSTEM_CLOCK);
}
public MonotonicClock(long monotonicTime, @NonNull Clock clock) {
+ this(null, monotonicTime, clock);
+ }
+
+ public MonotonicClock(@Nullable File file, long monotonicTime, @NonNull Clock clock) {
mClock = clock;
- mFile = null;
- mTimeshift = monotonicTime - mClock.elapsedRealtime();
+ if (file != null) {
+ mFile = new AtomicFile(file);
+ mTimeshift = read(monotonicTime - mClock.elapsedRealtime());
+ } else {
+ mFile = null;
+ mTimeshift = monotonicTime - mClock.elapsedRealtime();
+ }
}
/**
@@ -81,15 +88,16 @@ public class MonotonicClock {
return mTimeshift + elapsedRealtimeMs;
}
- private void read() {
+ private long read(long defaultTimeshift) {
if (!mFile.exists()) {
- return;
+ return defaultTimeshift;
}
try {
- readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser());
+ return readXml(new ByteArrayInputStream(mFile.readFully()), Xml.newBinaryPullParser());
} catch (IOException e) {
Log.e(TAG, "Cannot load monotonic clock from " + mFile.getBaseFile(), e);
+ return defaultTimeshift;
}
}
@@ -102,18 +110,21 @@ public class MonotonicClock {
return;
}
- try (FileOutputStream out = mFile.startWrite()) {
+ FileOutputStream out = null;
+ try {
+ out = mFile.startWrite();
writeXml(out, Xml.newBinarySerializer());
+ mFile.finishWrite(out);
} catch (IOException e) {
Log.e(TAG, "Cannot write monotonic clock to " + mFile.getBaseFile(), e);
+ mFile.failWrite(out);
}
}
/**
* Parses an XML file containing the persistent state of the monotonic clock.
*/
- @VisibleForTesting
- public void readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException {
+ private long readXml(InputStream inputStream, TypedXmlPullParser parser) throws IOException {
long savedTimeshift = 0;
try {
parser.setInput(inputStream, StandardCharsets.UTF_8.name());
@@ -128,14 +139,13 @@ public class MonotonicClock {
} catch (XmlPullParserException e) {
throw new IOException(e);
}
- mTimeshift = savedTimeshift - mClock.elapsedRealtime();
+ return savedTimeshift - mClock.elapsedRealtime();
}
/**
* Creates an XML file containing the persistent state of the monotonic clock.
*/
- @VisibleForTesting
- public void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
+ private void writeXml(OutputStream out, TypedXmlSerializer serializer) throws IOException {
serializer.setOutput(out, StandardCharsets.UTF_8.name());
serializer.startDocument(null, true);
serializer.startTag(null, XML_TAG_MONOTONIC_TIME);
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java
index e3bfb38802db..e419d13730c2 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java
@@ -38,7 +38,7 @@ import java.util.List;
public class ParsedAttributionImpl implements ParsedAttribution, Parcelable {
/** Maximum amount of attributions per package */
- static final int MAX_NUM_ATTRIBUTIONS = 10000;
+ static final int MAX_NUM_ATTRIBUTIONS = 400;
/** Tag of the attribution */
private @NonNull String tag;
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 03cfd4f2ab91..969f95db002d 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -80,4 +80,5 @@ oneway interface IPhoneStateListener {
void onMediaQualityStatusChanged(in MediaQualityStatus mediaQualityStatus);
void onCallBackModeStarted(int type);
void onCallBackModeStopped(int type, int reason);
+ void onSimultaneousCallingStateChanged(in int[] subIds);
}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index aab22421b334..0203ea49f252 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -104,6 +104,7 @@ interface ITelephonyRegistry {
void notifyAllowedNetworkTypesChanged(in int phoneId, in int subId, in int reason, in long allowedNetworkType);
void notifyLinkCapacityEstimateChanged(in int phoneId, in int subId,
in List<LinkCapacityEstimate> linkCapacityEstimateList);
+ void notifySimultaneousCellularCallingSubscriptionsChanged(in int[] subIds);
void addCarrierPrivilegesCallback(
int phoneId, ICarrierPrivilegesCallback callback, String pkg, String featureId);
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index b87027cbb6e7..419193688268 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -120,8 +120,9 @@ public class CollectionUtils {
public static @NonNull <I, O> List<O> map(@Nullable List<I> cur,
Function<? super I, ? extends O> f) {
if (isEmpty(cur)) return Collections.emptyList();
- final ArrayList<O> result = new ArrayList<>();
- for (int i = 0; i < cur.size(); i++) {
+ final int size = cur.size();
+ final ArrayList<O> result = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
result.add(f.apply(cur.get(i)));
}
return result;
@@ -133,7 +134,7 @@ public class CollectionUtils {
public static @NonNull <I, O> Set<O> map(@Nullable Set<I> cur,
Function<? super I, ? extends O> f) {
if (isEmpty(cur)) return emptySet();
- ArraySet<O> result = new ArraySet<>();
+ ArraySet<O> result = new ArraySet<>(cur.size());
if (cur instanceof ArraySet) {
ArraySet<I> arraySet = (ArraySet<I>) cur;
int size = arraySet.size();
@@ -163,7 +164,7 @@ public class CollectionUtils {
Function<? super I, ? extends O> f) {
if (isEmpty(cur)) return Collections.emptyList();
List<O> result = null;
- for (int i = 0; i < cur.size(); i++) {
+ for (int i = 0, size = cur.size(); i < size; i++) {
O transformed = f.apply(cur.get(i));
if (transformed != null) {
result = add(result, transformed);
@@ -239,7 +240,7 @@ public class CollectionUtils {
public static @NonNull <T> List<T> filter(@Nullable List<?> list, Class<T> c) {
if (isEmpty(list)) return Collections.emptyList();
ArrayList<T> result = null;
- for (int i = 0; i < list.size(); i++) {
+ for (int i = 0, size = list.size(); i < size; i++) {
final Object item = list.get(i);
if (c.isInstance(item)) {
result = ArrayUtils.add(result, (T) item);
@@ -273,7 +274,7 @@ public class CollectionUtils {
public static @Nullable <T> T find(@Nullable List<T> items,
java.util.function.Predicate<T> predicate) {
if (isEmpty(items)) return null;
- for (int i = 0; i < items.size(); i++) {
+ for (int i = 0, size = items.size(); i < size; i++) {
final T item = items.get(i);
if (predicate.test(item)) return item;
}
diff --git a/core/java/com/android/internal/util/RingBuffer.java b/core/java/com/android/internal/util/RingBuffer.java
index 04886598de35..342ba1b67e4d 100644
--- a/core/java/com/android/internal/util/RingBuffer.java
+++ b/core/java/com/android/internal/util/RingBuffer.java
@@ -19,7 +19,10 @@ package com.android.internal.util;
import static com.android.internal.util.Preconditions.checkArgumentPositive;
import java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
/**
* A simple ring buffer structure with bounded capacity backed by an array.
@@ -30,16 +33,35 @@ import java.util.Arrays;
@android.ravenwood.annotation.RavenwoodKeepWholeClass
public class RingBuffer<T> {
+ private final Supplier<T> mNewItem;
// Array for storing events.
private final T[] mBuffer;
// Cursor keeping track of the logical end of the array. This cursor never
// wraps and instead keeps track of the total number of append() operations.
private long mCursor = 0;
+ /**
+ * @deprecated This uses reflection to create new instances.
+ * Use {@link #RingBuffer(Supplier, IntFunction, int)}} instead.
+ */
+ @Deprecated
public RingBuffer(Class<T> c, int capacity) {
+ this(() -> (T) createNewItem(c), cap -> (T[]) Array.newInstance(c, cap), capacity);
+ }
+
+ private static Object createNewItem(Class c) {
+ try {
+ return c.getDeclaredConstructor().newInstance();
+ } catch (IllegalAccessException | InstantiationException | NoSuchMethodException
+ | InvocationTargetException e) {
+ return null;
+ }
+ }
+
+ public RingBuffer(Supplier<T> newItem, IntFunction<T[]> newBacking, int capacity) {
checkArgumentPositive(capacity, "A RingBuffer cannot have 0 capacity");
- // Java cannot create generic arrays without a runtime hint.
- mBuffer = (T[]) Array.newInstance(c, capacity);
+ mBuffer = newBacking.apply(capacity);
+ mNewItem = newItem;
}
public int size() {
@@ -69,22 +91,11 @@ public class RingBuffer<T> {
public T getNextSlot() {
final int nextSlotIdx = indexOf(mCursor++);
if (mBuffer[nextSlotIdx] == null) {
- mBuffer[nextSlotIdx] = createNewItem();
+ mBuffer[nextSlotIdx] = mNewItem.get();
}
return mBuffer[nextSlotIdx];
}
- /**
- * @return a new object of type <T> or null if a new object could not be created.
- */
- protected T createNewItem() {
- try {
- return (T) mBuffer.getClass().getComponentType().newInstance();
- } catch (IllegalAccessException | InstantiationException e) {
- return null;
- }
- }
-
public T[] toArray() {
// Only generic way to create a T[] from another T[]
T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass());
diff --git a/core/jni/android_hardware_OverlayProperties.cpp b/core/jni/android_hardware_OverlayProperties.cpp
index 5b95ee79f330..f6fe3dd842da 100644
--- a/core/jni/android_hardware_OverlayProperties.cpp
+++ b/core/jni/android_hardware_OverlayProperties.cpp
@@ -61,13 +61,12 @@ static jboolean android_hardware_OverlayProperties_supportFp16ForHdr(JNIEnv* env
static_cast<int32_t>(HAL_PIXEL_FORMAT_RGBA_FP16)) !=
i.pixelFormats.end() &&
std::find(i.standards.begin(), i.standards.end(),
- static_cast<int32_t>(HAL_DATASPACE_STANDARD_BT2020)) !=
+ static_cast<int32_t>(HAL_DATASPACE_STANDARD_BT709)) !=
i.standards.end() &&
std::find(i.transfers.begin(), i.transfers.end(),
- static_cast<int32_t>(HAL_DATASPACE_TRANSFER_ST2084)) !=
- i.transfers.end() &&
+ static_cast<int32_t>(HAL_DATASPACE_TRANSFER_SRGB)) != i.transfers.end() &&
std::find(i.ranges.begin(), i.ranges.end(),
- static_cast<int32_t>(HAL_DATASPACE_RANGE_FULL)) != i.ranges.end()) {
+ static_cast<int32_t>(HAL_DATASPACE_RANGE_EXTENDED)) != i.ranges.end()) {
return true;
}
}
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 55326da2c7df..25b2aaf3d737 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -1938,11 +1938,11 @@ static void nativeSetFrameTimelineVsync(JNIEnv* env, jclass clazz, jlong transac
transaction->setFrameTimelineInfo(ftInfo);
}
-static void nativeSetDesiredPresentTime(JNIEnv* env, jclass clazz, jlong transactionObj,
- jlong desiredPresentTime) {
+static void nativeSetDesiredPresentTimeNanos(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong desiredPresentTimeNanos) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
- transaction->setDesiredPresentTime(desiredPresentTime);
+ transaction->setDesiredPresentTime(desiredPresentTimeNanos);
}
static void nativeAddTransactionCommittedListener(JNIEnv* env, jclass clazz, jlong transactionObj,
@@ -2412,8 +2412,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
{"getNativeTrustedPresentationCallbackFinalizer", "()J", (void*)getNativeTrustedPresentationCallbackFinalizer },
{"nativeGetStalledTransactionInfo", "(I)Landroid/gui/StalledTransactionInfo;",
(void*) nativeGetStalledTransactionInfo },
- {"nativeSetDesiredPresentTime", "(JJ)V",
- (void*) nativeSetDesiredPresentTime },
+ {"nativeSetDesiredPresentTimeNanos", "(JJ)V",
+ (void*) nativeSetDesiredPresentTimeNanos },
// clang-format on
};
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5f3f6419418a..adea8000cebc 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3042,6 +3042,17 @@
<permission android:name="android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE"
android:protectionLevel="internal|role" />
+ <!-- Used to provide the Telecom framework with access to the last known call ID.
+ <p>Protection level: signature
+ @SystemApi
+ @FlaggedApi("com.android.server.telecom.flags.telecom_resolve_hidden_dependencies")
+ @hide
+ -->
+ <permission android:name="android.permission.ACCESS_LAST_KNOWN_CELL_ID"
+ android:protectionLevel="signature"
+ android:label="@string/permlab_accessLastKnownCellId"
+ android:description="@string/permdesc_accessLastKnownCellId"/>
+
<!-- ================================== -->
<!-- Permissions for sdcard interaction -->
<!-- ================================== -->
@@ -3160,7 +3171,7 @@
types of interactions
@hide -->
<permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
- android:protectionLevel="signature|installer|role" />
+ android:protectionLevel="signature|installer|module|role" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<!-- Allows interaction across profiles in the same profile group. -->
@@ -5055,7 +5066,7 @@
<p>Intended for use by ROLE_ASSISTANT and signature apps only.
-->
<permission android:name="android.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE"
- android:protectionLevel="signature|role"/>
+ android:protectionLevel="signature|module|role"/>
<!-- Must be required by a {@link android.service.autofill.AutofillService},
to ensure that only the system can bind to it.
@@ -5673,7 +5684,7 @@
<!-- @SystemApi Allows an application to manage the holders of a role.
@hide -->
<permission android:name="android.permission.MANAGE_ROLE_HOLDERS"
- android:protectionLevel="signature|installer" />
+ android:protectionLevel="signature|installer|module" />
<!-- @SystemApi Allows an application to manage the holders of roles associated with default
applications.
@@ -5693,7 +5704,7 @@
<!-- @SystemApi Allows an application to observe role holder changes.
@hide -->
<permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"
- android:protectionLevel="signature|installer" />
+ android:protectionLevel="signature|installer|module" />
<!-- Allows an application to manage the companion devices.
@hide -->
@@ -7896,6 +7907,26 @@
<permission android:name="android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS"
android:protectionLevel="signature|role" />
+ <!-- @SystemApi
+ @FlaggedApi("android.app.bic_client")
+ Allows app to call BackgroundInstallControlManager API to retrieve silently installed apps
+ for all users on device.
+ <p>Apps with a BackgroundInstallControlManager client will not be able to call any API without
+ this permission.
+ <p>Protection level: signature|role
+ @hide
+ -->
+ <permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES"
+ android:protectionLevel="signature|role" />
+
+ <!-- @SystemApi Allows an application to read the system grammatical gender.
+ @FlaggedApi("android.app.system_terms_of_address_enabled")
+ <p>Protection level: signature|privileged|appop
+ @hide
+ -->
+ <permission android:name="android.permission.READ_SYSTEM_GRAMMATICAL_GENDER"
+ android:protectionLevel="signature|privileged|appop"/>
+
<!-- Attribution for Geofencing service. -->
<attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
<!-- Attribution for Country Detector. -->
diff --git a/core/res/res/drawable-nodpi/platlogo.xml b/core/res/res/drawable-nodpi/platlogo.xml
index f3acab063bbf..fe12f6ee7836 100644
--- a/core/res/res/drawable-nodpi/platlogo.xml
+++ b/core/res/res/drawable-nodpi/platlogo.xml
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2021 The Android Open Source Project
+Copyright (C) 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -20,179 +20,103 @@ Copyright (C) 2021 The Android Open Source Project
android:viewportWidth="512"
android:viewportHeight="512">
<path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0">
+ android:pathData="M256.22,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98s-24.52,-54.37 -11.33,-77.21c13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21s-103.26,178.86 -120.65,208.98c-17.39,30.12 -34.83,48.42 -61.2,48.42Z"
+ android:strokeWidth="0">
<aapt:attr name="android:fillColor">
<gradient
- android:startX="256"
- android:startY="21.81"
- android:endX="256"
- android:endY="350.42"
+ android:startX="56.22"
+ android:startY="256"
+ android:endX="456.22"
+ android:endY="256"
android:type="linear">
<item android:offset="0" android:color="#FF073042"/>
<item android:offset="1" android:color="#FF073042"/>
</gradient>
</aapt:attr>
</path>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="m195.27,187.64l2.25,-6.69c13.91,78.13 50.84,284.39 50.84,50.33 0,-0.97 0.72,-1.81 1.62,-1.81h12.69c0.9,0 1.62,0.83 1.62,1.8 -0.2,409.91 -69.03,-43.64 -69.03,-43.64Z"
- android:fillColor="#3ddc84"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="m158.77,180.68l-33.17,57.45c-1.9,3.3 -0.77,7.52 2.53,9.42 3.3,1.9 7.52,0.77 9.42,-2.53l33.59,-58.17c54.27,24.33 116.34,24.33 170.61,0l33.59,58.17c1.97,3.26 6.21,4.3 9.47,2.33 3.17,-1.91 4.26,-5.99 2.47,-9.23l-33.16,-57.45c56.95,-30.97 95.91,-88.64 101.61,-156.76H57.17c5.7,68.12 44.65,125.79 101.61,156.76Z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M250.26,187.66L262.17,187.66A2.13,2.13 0,0 1,264.3 189.78L264.3,217.85A2.13,2.13 0,0 1,262.17 219.98L250.26,219.98A2.13,2.13 0,0 1,248.14 217.85L248.14,189.78A2.13,2.13 0,0 1,250.26 187.66z"
- android:fillColor="#3ddc84"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M250.12,170.29L262.32,170.29A1.98,1.98 0,0 1,264.3 172.26L264.3,176.39A1.98,1.98 0,0 1,262.32 178.37L250.12,178.37A1.98,1.98 0,0 1,248.14 176.39L248.14,172.26A1.98,1.98 0,0 1,250.12 170.29z"
- android:fillColor="#3ddc84"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M171.92,216.82h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M369.04,337.63h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M330.82,273.31h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M220.14,238.94h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M293.34,349.25h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M161.05,254.24h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M378.92,192h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <group>
- <clip-path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"/>
- <path
- android:pathData="M137.87,323.7h8.08v8.08h-8.08z"
- android:fillColor="#fff"/>
- </group>
- <path
- android:pathData="M256,256m-200,0a200,200 0,1 1,400 0a200,200 0,1 1,-400 0"
- android:strokeWidth="56.561"
- android:fillColor="#00000000"
- android:strokeColor="#f86734"/>
<path
- android:pathData="m256.22,126.57c-6.69,0 -12.12,5.27 -12.12,11.77v14.52c0,1.25 1.02,2.27 2.27,2.27h0c1.25,0 2.27,-1.02 2.27,-2.27v-3.91c0,-2.51 2.04,-4.55 4.55,-4.55h6.06c2.51,0 4.55,2.04 4.55,4.55v3.91c0,1.25 1.02,2.27 2.27,2.27s2.27,-1.02 2.27,-2.27v-14.52c0,-6.5 -5.43,-11.77 -12.12,-11.77Z"
+ android:pathData="M198.85,168.57l2.03,-6.03c12.55,70.48 45.87,256.56 45.87,45.41 0,-0.88 0.65,-1.63 1.46,-1.63h11.45c0.81,0 1.46,0.75 1.46,1.63 -0.18,369.8 -62.28,-39.37 -62.28,-39.37Z"
+ android:strokeWidth="0"
+ android:fillColor="#3ddc84"/>
+ <path
+ android:pathData="M186.69,167.97l-23.69,41.03c-1.36,2.36 -0.55,5.37 1.8,6.73 2.36,1.36 5.37,0.55 6.73,-1.8l23.99,-41.55c38.76,17.38 83.1,17.38 121.86,0l23.99,41.55c1.41,2.33 4.44,3.07 6.77,1.66 2.26,-1.37 3.04,-4.28 1.76,-6.59l-23.69,-41.03c40.68,-22.12 68.5,-63.31 72.57,-111.97H114.11c4.07,48.65 31.89,89.85 72.57,111.97Z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M248.46,168.59L259.2,168.59A1.92,1.92 0,0 1,261.12 170.5L261.12,195.83A1.92,1.92 0,0 1,259.2 197.75L248.46,197.75A1.92,1.92 0,0 1,246.54 195.83L246.54,170.5A1.92,1.92 0,0 1,248.46 168.59z"
+ android:strokeWidth="0"
+ android:fillColor="#3ddc84"/>
+ <path
+ android:pathData="M248.32,152.92L259.34,152.92A1.78,1.78 0,0 1,261.12 154.7L261.12,158.43A1.78,1.78 0,0 1,259.34 160.21L248.32,160.21A1.78,1.78 0,0 1,246.54 158.43L246.54,154.7A1.78,1.78 0,0 1,248.32 152.92z"
+ android:strokeWidth="0"
android:fillColor="#3ddc84"/>
<path
- android:pathData="m93.34,116.36l3.85,-4.36 29.64,9.76 -4.44,5.03 -6.23,-2.1 -7.86,8.91 2.86,5.92 -4.43,5.03 -13.39,-28.18ZM110.43,122.76l-8.86,-3.02 4.11,8.41 4.76,-5.39Z"
+ android:pathData="M159.03,176.91h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M188.8,275.73h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M373.41,158.93h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M112.1,129.34h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M285.93,252.22h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M318.96,218.84h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
+ android:fillColor="#fff"/>
+ <path
+ android:pathData="M294.05,288.55h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m153.62,100.85l-21.71,-6.2 10.38,14.38 -5.21,3.76 -16.78,-23.26 4.49,-3.24 21.65,6.19 -10.35,-14.35 5.24,-3.78 16.78,23.26 -4.49,3.24Z"
+ android:pathData="M330.82,263.31h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m161.46,63.15l8.99,-3.84c7.43,-3.18 15.96,0.12 19.09,7.44 3.13,7.32 -0.38,15.76 -7.81,18.94l-8.99,3.84 -11.28,-26.38ZM179.41,80.26c4.46,-1.91 5.96,-6.72 4.15,-10.96 -1.81,-4.24 -6.33,-6.48 -10.79,-4.57l-3.08,1.32 6.64,15.53 3.08,-1.32Z"
+ android:pathData="M188.8,298.95h4.04v4.04h-4.04z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m204.23,47.57l11.1,-2.2c5.31,-1.05 9.47,2.08 10.4,6.76 0.72,3.65 -0.76,6.37 -4.07,8.34l12.4,10.44 -7.57,1.5 -11.65,-9.76 -1.03,0.2 2.3,11.61 -6.3,1.25 -5.57,-28.14ZM216.78,56.7c1.86,-0.37 3,-1.71 2.68,-3.33 -0.34,-1.7 -1.88,-2.43 -3.74,-2.06l-4.04,0.8 1.07,5.39 4.04,-0.8Z"
+ android:pathData="M224.18,216.82h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m244.29,55.6c0.13,-8.16 6.86,-14.72 15.06,-14.58 8.16,0.13 14.72,6.9 14.58,15.06s-6.91,14.72 -15.06,14.58c-8.2,-0.13 -14.71,-6.9 -14.58,-15.06ZM267.44,55.98c0.08,-4.64 -3.54,-8.66 -8.18,-8.74 -4.68,-0.08 -8.42,3.82 -8.5,8.47 -0.08,4.65 3.54,8.66 8.22,8.74 4.64,0.08 8.39,-3.82 8.46,-8.47Z"
+ android:pathData="M272.1,318.9h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m294.39,44.84l6.31,1.23 -5.49,28.16 -6.31,-1.23 5.49,-28.16Z"
+ android:pathData="M293.34,339.25h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m321.94,51.41l9.14,3.48c7.55,2.88 11.39,11.17 8.56,18.61 -2.83,7.44 -11.22,11.07 -18.77,8.19l-9.14,-3.48 10.22,-26.8ZM322.96,76.19c4.53,1.73 8.95,-0.69 10.6,-5 1.64,-4.3 -0.05,-9.06 -4.58,-10.78l-3.13,-1.19 -6.01,15.78 3.13,1.19Z"
+ android:pathData="M165.09,236.28h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m381.41,89.24l-4.21,-3.21 3.65,-4.78 9.06,6.91 -17.4,22.81 -4.85,-3.7 13.75,-18.02Z"
+ android:pathData="M378.92,192h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
<path
- android:pathData="m397.96,126.37l-9.56,-10.26 3.61,-3.36 22.8,-1.25 3.74,4.02 -12.35,11.51 2.51,2.69 -4.08,3.8 -2.51,-2.69 -4.55,4.24 -4.16,-4.46 4.55,-4.24ZM407.83,117.17l-10.28,0.58 4.49,4.82 5.79,-5.4Z"
+ android:pathData="M204.28,314.86h8.08v8.08h-8.08z"
+ android:strokeWidth="0"
android:fillColor="#fff"/>
+ <path
+ android:pathData="M253.83,118.47c-6.04,0 -10.93,4.76 -10.93,10.62v13.1c0,1.13 0.92,2.05 2.05,2.05h0c1.13,0 2.05,-0.92 2.05,-2.05v-3.53c0,-2.27 1.84,-4.1 4.1,-4.1h5.47c2.27,0 4.1,1.84 4.1,4.1v3.53c0,1.13 0.92,2.05 2.05,2.05s2.05,-0.92 2.05,-2.05v-13.1c0,-5.86 -4.9,-10.62 -10.93,-10.62Z"
+ android:strokeWidth="0"
+ android:fillColor="#3ddc84"/>
+ <path
+ android:pathData="M256,437.7c-26.38,0 -43.81,-18.3 -61.2,-48.42 -17.39,-30.12 -103.26,-178.86 -120.65,-208.98 -17.39,-30.12 -24.52,-54.37 -11.33,-77.21 13.19,-22.84 37.75,-28.79 72.53,-28.79 34.78,0 206.53,0 241.31,0 34.78,0 59.35,5.95 72.53,28.79 13.19,22.84 6.06,47.09 -11.33,77.21 -17.39,30.12 -103.26,178.86 -120.65,208.98 -17.39,30.12 -34.83,48.42 -61.2,48.42Z"
+ android:strokeWidth="55"
+ android:fillColor="#00000000"
+ android:strokeColor="#f86733"/>
</vector>
-
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 5cfb1a3e44ba..2eb28eb555e8 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4372,6 +4372,14 @@
<attr name="name" />
</declare-styleable>
+ <!-- Specify one or more <code>polling-loop-filter</code> elements inside a
+ <code>host-apdu-service</code> to indicate polling loop frames that
+ your service can handle. -->
+ <declare-styleable name="PollingLoopFilter">
+ <!-- The polling loop frame. This attribute is mandatory. -->
+ <attr name="name" />
+ </declare-styleable>
+
<!-- Use <code>host-nfcf-service</code> as the root tag of the XML resource that
describes an {@link android.nfc.cardemulation.HostNfcFService} service, which
is referenced from its {@link android.nfc.cardemulation.HostNfcFService#SERVICE_META_DATA}
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 8fae6db4114a..29086a452ee6 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1813,6 +1813,12 @@
<attr name="allowUpdateOwnership" format="boolean" />
+ <!-- This attribute can be applied to any tag in the manifest. The system uses its value to
+ determine whether the element (e.g., a permission) should be enabled or disabled
+ depending on the state of feature flags.
+ @hide @SystemApi @FlaggedApi("android.content.res.manifest_flagging") -->
+ <attr name="featureFlag" format="string" />
+
<!-- The <code>manifest</code> tag is the root of an
<code>AndroidManifest.xml</code> file,
describing the contents of an Android package (.apk) file. One
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 28adccd6b0a1..5be29a6d68b8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -57,7 +57,6 @@
<item><xliff:g id="id">@string/status_bar_screen_record</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_cast</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_ethernet</xliff:g></item>
- <item><xliff:g id="id">@string/status_bar_oem_satellite</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_wifi</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_hotspot</xliff:g></item>
<item><xliff:g id="id">@string/status_bar_mobile</xliff:g></item>
@@ -103,7 +102,6 @@
<string translatable="false" name="status_bar_call_strength">call_strength</string>
<string translatable="false" name="status_bar_sensors_off">sensors_off</string>
<string translatable="false" name="status_bar_screen_record">screen_record</string>
- <string translatable="false" name="status_bar_oem_satellite">satellite</string>
<!-- Flag indicating whether the surface flinger has limited
alpha compositing functionality in hardware. If set, the window
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 53464547c272..d0216b308a4c 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -192,8 +192,13 @@
<!-- The identifier of the satellite's SIM profile. The identifier is composed of MCC and MNC
of the satellite PLMN with the format "mccmnc". -->
- <string name="config_satellite_sim_identifier" translatable="false"></string>
- <java-symbol type="string" name="config_satellite_sim_identifier" />
+ <string name="config_satellite_sim_plmn_identifier" translatable="false"></string>
+ <java-symbol type="string" name="config_satellite_sim_plmn_identifier" />
+
+ <!-- The identifier for the satellite's SIM profile. The identifier is the service provider name
+ (spn) from the profile metadata. -->
+ <string name="config_satellite_sim_spn_identifier" translatable="false"></string>
+ <java-symbol type="string" name="config_satellite_sim_spn_identifier" />
<!-- The app to which the emergency call will be handed over for OEM-enabled satellite
messaging. The format of the config string is "package_name;class_name". -->
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 53b473e0ac1f..7d2288599841 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -119,6 +119,8 @@
<public name="optional"/>
<!-- @FlaggedApi("android.media.tv.flags.enable_ad_service_fw") -->
<public name="adServiceTypes" />
+ <!-- @hide @SystemApi @FlaggedApi("android.content.res.manifest_flagging") -->
+ <public name="featureFlag"/>
</staging-public-group>
<staging-public-group type="id" first-id="0x01bc0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index f2858066b55b..bd3a5e4fe67c 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1075,6 +1075,12 @@
<string name="permdesc_manageOngoingCalls">Allows an app to see details about ongoing calls
on your device and to control these calls.</string>
+ <!-- Title for an application permission, listed so that the user can access the last known cell id provided by telephony. -->
+ <string name="permlab_accessLastKnownCellId">Access last known cell identity.</string>
+ <!-- Description on an application permission, listed so that the user can access the last known cell id provided by telephony -->
+ <string name="permdesc_accessLastKnownCellId">Allows an app to access to the last known
+ cell identity provided by telephony.</string>
+
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_readCellBroadcasts">read cell broadcast messages</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ef12d8f4ac0f..d12ef2b95f06 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3198,7 +3198,6 @@
<java-symbol type="string" name="status_bar_camera" />
<java-symbol type="string" name="status_bar_sensors_off" />
<java-symbol type="string" name="status_bar_screen_record" />
- <java-symbol type="string" name="status_bar_oem_satellite" />
<!-- Locale picker -->
<java-symbol type="id" name="locale_search_menu" />
diff --git a/core/tests/BroadcastRadioTests/TEST_MAPPING b/core/tests/BroadcastRadioTests/TEST_MAPPING
new file mode 100644
index 000000000000..b085a27b25b8
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "BroadcastRadioTests"
+ }
+ ]
+}
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java
index b1cf9c2d4422..bb69fa42a63a 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramSelectorTest.java
@@ -124,6 +124,17 @@ public final class ProgramSelectorTest {
}
@Test
+ public void construct_withNullInSecondaryIdsForSelector() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ new ProgramSelector(DAB_PROGRAM_TYPE, DAB_DMB_SID_EXT_IDENTIFIER_1,
+ new ProgramSelector.Identifier[]{null}, /* vendorIds= */ null);
+ });
+
+ assertWithMessage("Exception for null secondary id")
+ .that(thrown).hasMessageThat().contains("secondaryIds list must not contain nulls");
+ }
+
+ @Test
public void getProgramType() {
ProgramSelector selector = getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null);
@@ -269,11 +280,11 @@ public final class ProgramSelectorTest {
ProgramSelector selector = ProgramSelector.createAmFmSelector(band, (int) AM_FREQUENCY);
- assertWithMessage("Program type")
+ assertWithMessage("AM program type without subchannel")
.that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_AM);
- assertWithMessage("Primary identifiers")
+ assertWithMessage("AM primary identifiers without subchannel")
.that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
- assertWithMessage("Secondary identifiers")
+ assertWithMessage("AM secondary identifiers without subchannel")
.that(selector.getSecondaryIds()).isEmpty();
}
@@ -285,15 +296,29 @@ public final class ProgramSelectorTest {
ProgramSelector selector = ProgramSelector.createAmFmSelector(
RadioManager.BAND_INVALID, (int) FM_FREQUENCY);
- assertWithMessage("Program type")
+ assertWithMessage("FM program type without band and subchannel")
.that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_FM);
- assertWithMessage("Primary identifiers")
+ assertWithMessage("FM primary identifiers without band and subchannel")
.that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
- assertWithMessage("Secondary identifiers")
+ assertWithMessage("FM secondary identifiers without band and subchannel")
.that(selector.getSecondaryIds()).isEmpty();
}
@Test
+ public void createAmFmSelector_withValidFrequency() {
+ ProgramSelector.Identifier primaryIdExpected = new ProgramSelector.Identifier(
+ ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, AM_FREQUENCY);
+
+ ProgramSelector selector = ProgramSelector.createAmFmSelector(RadioManager.BAND_INVALID,
+ (int) AM_FREQUENCY);
+
+ assertWithMessage("AM program type with valid frequency")
+ .that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_AM);
+ assertWithMessage("AM primary identifiers with valid frequency")
+ .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
+ }
+
+ @Test
public void createAmFmSelector_withValidFrequencyAndSubChannel() {
int band = RadioManager.BAND_AM_HD;
int subChannel = 2;
@@ -307,15 +332,26 @@ public final class ProgramSelectorTest {
ProgramSelector selector = ProgramSelector.createAmFmSelector(band, (int) AM_FREQUENCY,
subChannel);
- assertWithMessage("Program type")
+ assertWithMessage("AM program type with valid frequency and subchannel")
.that(selector.getProgramType()).isEqualTo(ProgramSelector.PROGRAM_TYPE_AM);
- assertWithMessage("Primary identifiers")
+ assertWithMessage("AM primary identifiers with valid frequency and subchannel")
.that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
- assertWithMessage("Secondary identifiers")
+ assertWithMessage("AM secondary identifiers with valid frequency and subchannel")
.that(selector.getSecondaryIds()).isEqualTo(secondaryIdExpected);
}
@Test
+ public void createAmFmSelector_withInvalidBand_throwsIllegalArgumentException() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ ProgramSelector.createAmFmSelector(/* band= */ 1000, (int) AM_FREQUENCY);
+ });
+
+ assertWithMessage("Exception for using invalid band")
+ .that(thrown).hasMessageThat().contains(
+ "Unknown band");
+ }
+
+ @Test
public void createAmFmSelector_withInvalidFrequency_throwsIllegalArgumentException() {
int invalidFrequency = 50000;
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
index 89464d14d1c7..bbac69f35e81 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java
@@ -169,6 +169,16 @@ public final class RadioManagerTest {
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Test
+ public void constructor_withUnsupportedTypeForBandDescriptor_throwsException() {
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+ () -> new RadioManager.AmBandDescriptor(REGION, /* type= */ 100, AM_LOWER_LIMIT,
+ AM_UPPER_LIMIT, AM_SPACING, STEREO_SUPPORTED));
+
+ assertWithMessage("Unsupported band type exception")
+ .that(thrown).hasMessageThat().contains("Unsupported band");
+ }
+
+ @Test
public void getType_forBandDescriptor() {
RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor();
@@ -363,6 +373,18 @@ public final class RadioManagerTest {
}
@Test
+ public void equals_withAmBandDescriptorsAndOtherTypeObject() {
+ assertWithMessage("AM Band Descriptor")
+ .that(AM_BAND_DESCRIPTOR).isNotEqualTo(FM_BAND_DESCRIPTOR);
+ }
+
+ @Test
+ public void equals_withFmBandDescriptorsAndOtherTypeObject() {
+ assertWithMessage("FM Band Descriptor")
+ .that(FM_BAND_DESCRIPTOR).isNotEqualTo(AM_BAND_DESCRIPTOR);
+ }
+
+ @Test
public void equals_withAmBandDescriptorsOfDifferentUpperLimits_returnsFalse() {
RadioManager.AmBandDescriptor amBandDescriptorCompared =
new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM, AM_LOWER_LIMIT,
@@ -373,9 +395,83 @@ public final class RadioManagerTest {
}
@Test
- public void equals_withAmAndFmBandDescriptors_returnsFalse() {
- assertWithMessage("AM Band Descriptor")
- .that(AM_BAND_DESCRIPTOR).isNotEqualTo(FM_BAND_DESCRIPTOR);
+ public void equals_withAmBandDescriptorsOfDifferentStereoSupportValues() {
+ RadioManager.AmBandDescriptor amBandDescriptorCompared =
+ new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM, AM_LOWER_LIMIT,
+ AM_UPPER_LIMIT, AM_SPACING, !STEREO_SUPPORTED);
+
+ assertWithMessage("AM Band Descriptor of different stereo support values")
+ .that(AM_BAND_DESCRIPTOR).isNotEqualTo(amBandDescriptorCompared);
+ }
+
+ @Test
+ public void equals_withFmBandDescriptorsOfDifferentSpacingValues() {
+ RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+ REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING * 2,
+ STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+ assertWithMessage("FM Band Descriptors of different support limit values")
+ .that(FM_BAND_DESCRIPTOR).isNotEqualTo(fmBandDescriptorCompared);
+ }
+
+ @Test
+ public void equals_withFmBandConfigsOfDifferentLowerLimitValues() {
+ RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+ REGION + 1, RadioManager.BAND_AM_HD, AM_LOWER_LIMIT, AM_UPPER_LIMIT, AM_SPACING,
+ STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+ assertWithMessage("FM Band Descriptors of different region values")
+ .that(FM_BAND_DESCRIPTOR).isNotEqualTo(fmBandDescriptorCompared);
+ }
+
+ @Test
+ public void equals_withFmBandDescriptorsOfDifferentStereoSupportValues() {
+ RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+ REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+ !STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+ assertWithMessage("FM Band Descriptors of different stereo support values")
+ .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
+ }
+
+ @Test
+ public void equals_withFmBandDescriptorsOfDifferentRdsSupportValues() {
+ RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+ REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+ STEREO_SUPPORTED, !RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+ assertWithMessage("FM Band Descriptors of different rds support values")
+ .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
+ }
+
+ @Test
+ public void equals_withFmBandDescriptorsOfDifferentTaSupportValues() {
+ RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+ REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+ STEREO_SUPPORTED, RDS_SUPPORTED, !TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED);
+
+ assertWithMessage("FM Band Descriptors of different ta support values")
+ .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
+ }
+
+ @Test
+ public void equals_withFmBandDescriptorsOfDifferentAfSupportValues() {
+ RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+ REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+ STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, !AF_SUPPORTED, EA_SUPPORTED);
+
+ assertWithMessage("FM Band Descriptors of different af support values")
+ .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
+ }
+
+ @Test
+ public void equals_withFmBandDescriptorsOfDifferentEaSupportValues() {
+ RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+ REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+ STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, AF_SUPPORTED, !EA_SUPPORTED);
+
+ assertWithMessage("FM Band Descriptors of different ea support values")
+ .that(fmBandDescriptorCompared).isNotEqualTo(FM_BAND_DESCRIPTOR);
}
@Test
@@ -587,18 +683,79 @@ public final class RadioManagerTest {
}
@Test
- public void equals_withFmBandConfigsOfDifferentAfs_returnsFalse() {
- RadioManager.FmBandConfig.Builder builder = new RadioManager.FmBandConfig.Builder(
- createFmBandDescriptor()).setStereo(STEREO_SUPPORTED).setRds(RDS_SUPPORTED)
- .setTa(TA_SUPPORTED).setAf(!AF_SUPPORTED).setEa(EA_SUPPORTED);
- RadioManager.FmBandConfig fmBandConfigFromBuilder = builder.build();
+ public void equals_withFmBandConfigsOfDifferentRegionValues() {
+ RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+ new RadioManager.FmBandDescriptor(REGION + 1, RadioManager.BAND_AM_HD,
+ AM_LOWER_LIMIT, AM_UPPER_LIMIT, AM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED,
+ TA_SUPPORTED, AF_SUPPORTED, EA_SUPPORTED));
+
+ assertWithMessage("FM Band Config of different regions")
+ .that(FM_BAND_CONFIG).isNotEqualTo(fmBandConfigCompared);
+ }
+
+ @Test
+ public void equals_withFmBandConfigsOfDifferentStereoSupportValues() {
+ RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+ new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+ FM_UPPER_LIMIT, FM_SPACING, !STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED,
+ AF_SUPPORTED, EA_SUPPORTED));
+
+ assertWithMessage("FM Band Config with different stereo support values")
+ .that(fmBandConfigCompared).isNotEqualTo(FM_BAND_CONFIG);
+ }
- assertWithMessage("FM Band Config of different af value")
- .that(FM_BAND_CONFIG).isNotEqualTo(fmBandConfigFromBuilder);
+ @Test
+ public void equals_withFmBandConfigsOfDifferentRdsSupportValues() {
+ RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+ new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+ FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, !RDS_SUPPORTED, TA_SUPPORTED,
+ AF_SUPPORTED, EA_SUPPORTED));
+
+ assertWithMessage("FM Band Config with different RDS support values")
+ .that(fmBandConfigCompared).isNotEqualTo(FM_BAND_CONFIG);
+ }
+
+ @Test
+ public void equals_withFmBandConfigsOfDifferentTaSupportValues() {
+ RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+ new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+ FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED, !TA_SUPPORTED,
+ AF_SUPPORTED, EA_SUPPORTED));
+
+ assertWithMessage("FM Band Configs with different ta values")
+ .that(fmBandConfigCompared).isNotEqualTo(FM_BAND_CONFIG);
+ }
+
+ @Test
+ public void equals_withFmBandConfigsOfDifferentAfSupportValues() {
+ RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+ new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+ FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED,
+ !AF_SUPPORTED, EA_SUPPORTED));
+
+ assertWithMessage("FM Band Config of different af support value")
+ .that(FM_BAND_CONFIG).isNotEqualTo(fmBandConfigCompared);
+ }
+
+ @Test
+ public void equals_withFmBandConfigsOfDifferentEaSupportValues() {
+ RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+ new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT,
+ FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED,
+ AF_SUPPORTED, !EA_SUPPORTED));
+
+ assertWithMessage("FM Band Configs with different ea support values")
+ .that(fmBandConfigCompared).isNotEqualTo(FM_BAND_CONFIG);
+ }
+
+ @Test
+ public void equals_withAmBandConfigsAndOtherTypeObject() {
+ assertWithMessage("AM Band Config")
+ .that(AM_BAND_CONFIG).isNotEqualTo(FM_BAND_CONFIG);
}
@Test
- public void equals_withFmAndAmBandConfigs_returnsFalse() {
+ public void equals_withFmBandConfigsAndOtherTypeObject() {
assertWithMessage("FM Band Config")
.that(FM_BAND_CONFIG).isNotEqualTo(AM_BAND_CONFIG);
}
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
index 7b9121eb6085..fddfd397d068 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioMetadataTest.java
@@ -16,14 +16,20 @@
package android.hardware.radio;
-import static com.google.common.truth.Truth.assertWithMessage;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
import android.graphics.Bitmap;
import android.os.Parcel;
import android.platform.test.flag.junit.SetFlagsRule;
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase;
+
+import com.google.common.truth.Expect;
+
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -34,7 +40,7 @@ import java.util.Arrays;
import java.util.Set;
@RunWith(MockitoJUnitRunner.class)
-public final class RadioMetadataTest {
+public final class RadioMetadataTest extends ExtendedRadioMockitoTestCase {
private static final int CREATOR_ARRAY_SIZE = 3;
private static final int INT_KEY_VALUE = 1;
@@ -49,14 +55,21 @@ public final class RadioMetadataTest {
private Bitmap mBitmapValue;
@Rule
+ public final Expect mExpect = Expect.create();
+ @Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+ @Override
+ protected void initializeSession(StaticMockitoSessionBuilder builder) {
+ builder.spyStatic(Bitmap.class);
+ }
+
@Test
public void describeContents_forClock() {
RadioMetadata.Clock clock = new RadioMetadata.Clock(TEST_UTC_SECOND_SINCE_EPOCH,
TEST_TIME_ZONE_OFFSET_MINUTES);
- assertWithMessage("Describe contents for metadata clock")
+ mExpect.withMessage("Describe contents for metadata clock")
.that(clock.describeContents()).isEqualTo(0);
}
@@ -64,7 +77,7 @@ public final class RadioMetadataTest {
public void newArray_forClockCreator() {
RadioMetadata.Clock[] clocks = RadioMetadata.Clock.CREATOR.newArray(CREATOR_ARRAY_SIZE);
- assertWithMessage("Clock array size").that(clocks.length).isEqualTo(CREATOR_ARRAY_SIZE);
+ mExpect.withMessage("Clock array size").that(clocks.length).isEqualTo(CREATOR_ARRAY_SIZE);
}
@Test
@@ -77,9 +90,9 @@ public final class RadioMetadataTest {
parcel.setDataPosition(0);
RadioMetadata.Clock clockFromParcel = RadioMetadata.Clock.CREATOR.createFromParcel(parcel);
- assertWithMessage("UTC second since epoch of metadata clock created from parcel")
+ mExpect.withMessage("UTC second since epoch of metadata clock created from parcel")
.that(clockFromParcel.getUtcEpochSeconds()).isEqualTo(TEST_UTC_SECOND_SINCE_EPOCH);
- assertWithMessage("Time zone offset minutes of metadata clock created from parcel")
+ mExpect.withMessage("Time zone offset minutes of metadata clock created from parcel")
.that(clockFromParcel.getTimezoneOffsetMinutes())
.isEqualTo(TEST_TIME_ZONE_OFFSET_MINUTES);
}
@@ -92,7 +105,7 @@ public final class RadioMetadataTest {
mBuilder.putString(invalidStringKey, "value");
});
- assertWithMessage("Exception for putting illegal string-value key %s", invalidStringKey)
+ mExpect.withMessage("Exception for putting illegal string-value key %s", invalidStringKey)
.that(thrown).hasMessageThat()
.matches(".*" + invalidStringKey + ".*cannot.*String.*?");
}
@@ -105,12 +118,25 @@ public final class RadioMetadataTest {
mBuilder.putInt(invalidIntKey, INT_KEY_VALUE);
});
- assertWithMessage("Exception for putting illegal int-value for key %s", invalidIntKey)
+ mExpect.withMessage("Exception for putting illegal int-value for key %s", invalidIntKey)
.that(thrown).hasMessageThat()
.matches(".*" + invalidIntKey + ".*cannot.*int.*?");
}
@Test
+ public void putBitmap_withIllegalKey() {
+ String invalidIntKey = RadioMetadata.METADATA_KEY_GENRE;
+
+ IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> {
+ mBuilder.putBitmap(invalidIntKey, mBitmapValue);
+ });
+
+ mExpect.withMessage("Exception for putting illegal bitmap-value for key %s", invalidIntKey)
+ .that(thrown).hasMessageThat()
+ .matches(".*" + invalidIntKey + ".*cannot.*Bitmap.*?");
+ }
+
+ @Test
public void putClock_withIllegalKey() {
String invalidClockKey = RadioMetadata.METADATA_KEY_ALBUM;
@@ -119,7 +145,7 @@ public final class RadioMetadataTest {
/* timezoneOffsetMinutes= */ 1);
});
- assertWithMessage("Exception for putting illegal clock-value key %s", invalidClockKey)
+ mExpect.withMessage("Exception for putting illegal clock-value key %s", invalidClockKey)
.that(thrown).hasMessageThat()
.matches(".*" + invalidClockKey + ".*cannot.*Clock.*?");
}
@@ -133,7 +159,7 @@ public final class RadioMetadataTest {
mBuilder.putStringArray(invalidStringArrayKey, UFIDS_VALUE);
});
- assertWithMessage("Exception for putting illegal string-array-value for key %s",
+ mExpect.withMessage("Exception for putting illegal string-array-value for key %s",
invalidStringArrayKey).that(thrown).hasMessageThat()
.matches(".*" + invalidStringArrayKey + ".*cannot.*Array.*?");
}
@@ -146,7 +172,7 @@ public final class RadioMetadataTest {
mBuilder.putStringArray(/* key= */ null, UFIDS_VALUE);
});
- assertWithMessage("Exception for putting string-array with null key")
+ mExpect.withMessage("Exception for putting string-array with null key")
.that(thrown).hasMessageThat().contains("can not be null");
}
@@ -158,7 +184,7 @@ public final class RadioMetadataTest {
mBuilder.putStringArray(RadioMetadata.METADATA_KEY_UFIDS, /* value= */ null);
});
- assertWithMessage("Exception for putting null string-array")
+ mExpect.withMessage("Exception for putting null string-array")
.that(thrown).hasMessageThat().contains("can not be null");
}
@@ -167,7 +193,7 @@ public final class RadioMetadataTest {
String key = RadioMetadata.METADATA_KEY_RDS_PI;
RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
- assertWithMessage("Whether metadata contains %s in metadata", key)
+ mExpect.withMessage("Whether metadata contains %s in metadata", key)
.that(metadata.containsKey(key)).isTrue();
}
@@ -176,7 +202,7 @@ public final class RadioMetadataTest {
String key = RadioMetadata.METADATA_KEY_RDS_PI;
RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
- assertWithMessage("Whether metadata contains key %s not in metadata", key)
+ mExpect.withMessage("Whether metadata contains key %s not in metadata", key)
.that(metadata.containsKey(RadioMetadata.METADATA_KEY_ARTIST)).isFalse();
}
@@ -185,7 +211,7 @@ public final class RadioMetadataTest {
String key = RadioMetadata.METADATA_KEY_RDS_PI;
RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
- assertWithMessage("Int value for key %s in metadata", key)
+ mExpect.withMessage("Int value for key %s in metadata", key)
.that(metadata.getInt(key)).isEqualTo(INT_KEY_VALUE);
}
@@ -195,7 +221,7 @@ public final class RadioMetadataTest {
RadioMetadata metadata =
mBuilder.putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE).build();
- assertWithMessage("Int value for key %s in metadata", key)
+ mExpect.withMessage("Int value for key %s in metadata", key)
.that(metadata.getInt(key)).isEqualTo(0);
}
@@ -204,7 +230,7 @@ public final class RadioMetadataTest {
String key = RadioMetadata.METADATA_KEY_ARTIST;
RadioMetadata metadata = mBuilder.putString(key, ARTIST_KEY_VALUE).build();
- assertWithMessage("String value for key %s in metadata", key)
+ mExpect.withMessage("String value for key %s in metadata", key)
.that(metadata.getString(key)).isEqualTo(ARTIST_KEY_VALUE);
}
@@ -213,7 +239,7 @@ public final class RadioMetadataTest {
String key = RadioMetadata.METADATA_KEY_ARTIST;
RadioMetadata metadata = mBuilder.build();
- assertWithMessage("String value for key %s not in metadata", key)
+ mExpect.withMessage("String value for key %s not in metadata", key)
.that(metadata.getString(key)).isNull();
}
@@ -222,7 +248,7 @@ public final class RadioMetadataTest {
String key = RadioMetadata.METADATA_KEY_ICON;
RadioMetadata metadata = mBuilder.putBitmap(key, mBitmapValue).build();
- assertWithMessage("Bitmap value for key %s in metadata", key)
+ mExpect.withMessage("Bitmap value for key %s in metadata", key)
.that(metadata.getBitmap(key)).isEqualTo(mBitmapValue);
}
@@ -231,16 +257,26 @@ public final class RadioMetadataTest {
String key = RadioMetadata.METADATA_KEY_ICON;
RadioMetadata metadata = mBuilder.build();
- assertWithMessage("Bitmap value for key %s not in metadata", key)
+ mExpect.withMessage("Bitmap value for key %s not in metadata", key)
.that(metadata.getBitmap(key)).isNull();
}
@Test
+ public void getBitmap_withIllegalKey() {
+ String illegalKey = RadioMetadata.METADATA_KEY_ARTIST;
+ RadioMetadata metadata = mBuilder.putInt(RadioMetadata.METADATA_KEY_ICON, INT_KEY_VALUE)
+ .build();
+
+ mExpect.withMessage("Bitmap id value with non-bitmap-type key %s", illegalKey)
+ .that(metadata.getBitmap(illegalKey)).isNull();
+ }
+
+ @Test
public void getBitmapId_withKeyInMetadata() {
String key = RadioMetadata.METADATA_KEY_ART;
RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
- assertWithMessage("Bitmap id value for key %s in metadata", key)
+ mExpect.withMessage("Bitmap id value for key %s in metadata", key)
.that(metadata.getBitmapId(key)).isEqualTo(INT_KEY_VALUE);
}
@@ -249,11 +285,21 @@ public final class RadioMetadataTest {
String key = RadioMetadata.METADATA_KEY_ART;
RadioMetadata metadata = mBuilder.build();
- assertWithMessage("Bitmap id value for key %s not in metadata", key)
+ mExpect.withMessage("Bitmap id value for key %s not in metadata", key)
.that(metadata.getBitmapId(key)).isEqualTo(0);
}
@Test
+ public void getBitmapId_withIllegalKey() {
+ String illegalKey = RadioMetadata.METADATA_KEY_ARTIST;
+ RadioMetadata metadata = mBuilder.putInt(RadioMetadata.METADATA_KEY_ART, INT_KEY_VALUE)
+ .build();
+
+ mExpect.withMessage("Bitmap id value with non-bitmap-id-type key %s", illegalKey)
+ .that(metadata.getBitmapId(illegalKey)).isEqualTo(0);
+ }
+
+ @Test
public void getClock_withKeyInMetadata() {
String key = RadioMetadata.METADATA_KEY_CLOCK;
RadioMetadata metadata = mBuilder
@@ -262,10 +308,10 @@ public final class RadioMetadataTest {
RadioMetadata.Clock clockExpected = metadata.getClock(key);
- assertWithMessage("Number of seconds since epoch of value for key %s in metadata", key)
+ mExpect.withMessage("Number of seconds since epoch of value for key %s in metadata", key)
.that(clockExpected.getUtcEpochSeconds())
.isEqualTo(TEST_UTC_SECOND_SINCE_EPOCH);
- assertWithMessage("Offset of timezone in minutes of value for key %s in metadata", key)
+ mExpect.withMessage("Offset of timezone in minutes of value for key %s in metadata", key)
.that(clockExpected.getTimezoneOffsetMinutes())
.isEqualTo(TEST_TIME_ZONE_OFFSET_MINUTES);
}
@@ -275,17 +321,27 @@ public final class RadioMetadataTest {
String key = RadioMetadata.METADATA_KEY_CLOCK;
RadioMetadata metadata = mBuilder.build();
- assertWithMessage("Clock value for key %s not in metadata", key)
+ mExpect.withMessage("Clock value for key %s not in metadata", key)
.that(metadata.getClock(key)).isNull();
}
@Test
+ public void getClock_withIllegalKey() {
+ String illegalKey = RadioMetadata.METADATA_KEY_ARTIST;
+ RadioMetadata metadata = mBuilder.putClock(RadioMetadata.METADATA_KEY_CLOCK,
+ TEST_UTC_SECOND_SINCE_EPOCH, TEST_TIME_ZONE_OFFSET_MINUTES).build();
+
+ mExpect.withMessage("Clock value for non-clock-type key %s", illegalKey)
+ .that(metadata.getClock(illegalKey)).isNull();
+ }
+
+ @Test
public void getStringArray_withKeyInMetadata() {
mSetFlagsRule.enableFlags(Flags.FLAG_HD_RADIO_IMPROVED);
String key = RadioMetadata.METADATA_KEY_UFIDS;
RadioMetadata metadata = mBuilder.putStringArray(key, UFIDS_VALUE).build();
- assertWithMessage("String-array value for key %s not in metadata", key)
+ mExpect.withMessage("String-array value for key %s not in metadata", key)
.that(metadata.getStringArray(key)).asList().isEqualTo(Arrays.asList(UFIDS_VALUE));
}
@@ -299,7 +355,7 @@ public final class RadioMetadataTest {
metadata.getStringArray(key);
});
- assertWithMessage("Exception for getting string array for string-array value for key %s "
+ mExpect.withMessage("Exception for getting string array for string-array value for key %s "
+ "not in metadata", key).that(thrown).hasMessageThat().contains("not found");
}
@@ -312,7 +368,7 @@ public final class RadioMetadataTest {
metadata.getStringArray(/* key= */ null);
});
- assertWithMessage("Exception for getting string array with null key")
+ mExpect.withMessage("Exception for getting string array with null key")
.that(thrown).hasMessageThat().contains("can not be null");
}
@@ -326,7 +382,7 @@ public final class RadioMetadataTest {
metadata.getStringArray(invalidClockKey);
});
- assertWithMessage("Exception for getting string array for key %s not of string-array type",
+ mExpect.withMessage("Exception for getting string array for non-string-array type key %s",
invalidClockKey).that(thrown).hasMessageThat()
.contains("string array");
}
@@ -338,7 +394,7 @@ public final class RadioMetadataTest {
.putString(RadioMetadata.METADATA_KEY_ARTIST, ARTIST_KEY_VALUE)
.build();
- assertWithMessage("Size of fields in non-empty metadata")
+ mExpect.withMessage("Size of fields in non-empty metadata")
.that(metadata.size()).isEqualTo(2);
}
@@ -346,7 +402,7 @@ public final class RadioMetadataTest {
public void size_withEmptyMetadata() {
RadioMetadata metadata = mBuilder.build();
- assertWithMessage("Size of fields in empty metadata")
+ mExpect.withMessage("Size of fields in empty metadata")
.that(metadata.size()).isEqualTo(0);
}
@@ -360,7 +416,7 @@ public final class RadioMetadataTest {
Set<String> metadataSet = metadata.keySet();
- assertWithMessage("Metadata set of non-empty metadata")
+ mExpect.withMessage("Metadata set of non-empty metadata")
.that(metadataSet).containsExactly(RadioMetadata.METADATA_KEY_ICON,
RadioMetadata.METADATA_KEY_RDS_PI, RadioMetadata.METADATA_KEY_ARTIST);
}
@@ -371,7 +427,7 @@ public final class RadioMetadataTest {
Set<String> metadataSet = metadata.keySet();
- assertWithMessage("Metadata set of empty metadata")
+ mExpect.withMessage("Metadata set of empty metadata")
.that(metadataSet).isEmpty();
}
@@ -380,7 +436,7 @@ public final class RadioMetadataTest {
int nativeKey = 0;
String key = RadioMetadata.getKeyFromNativeKey(nativeKey);
- assertWithMessage("Key for native key %s", nativeKey)
+ mExpect.withMessage("Key for native key %s", nativeKey)
.that(key).isEqualTo(RadioMetadata.METADATA_KEY_RDS_PI);
}
@@ -393,7 +449,7 @@ public final class RadioMetadataTest {
RadioMetadata.Builder copyBuilder = new RadioMetadata.Builder(metadata);
RadioMetadata metadataCopied = copyBuilder.build();
- assertWithMessage("Metadata with the same contents")
+ mExpect.withMessage("Metadata with the same contents")
.that(metadataCopied).isEqualTo(metadata);
}
@@ -401,14 +457,15 @@ public final class RadioMetadataTest {
public void describeContents_forMetadata() {
RadioMetadata metadata = mBuilder.build();
- assertWithMessage("Metadata contents").that(metadata.describeContents()).isEqualTo(0);
+ mExpect.withMessage("Metadata contents").that(metadata.describeContents()).isEqualTo(0);
}
@Test
public void newArray_forRadioMetadataCreator() {
RadioMetadata[] metadataArray = RadioMetadata.CREATOR.newArray(CREATOR_ARRAY_SIZE);
- assertWithMessage("Radio metadata array").that(metadataArray).hasLength(CREATOR_ARRAY_SIZE);
+ mExpect.withMessage("Radio metadata array").that(metadataArray)
+ .hasLength(CREATOR_ARRAY_SIZE);
}
@Test
@@ -423,7 +480,7 @@ public final class RadioMetadataTest {
parcel.setDataPosition(0);
RadioMetadata metadataFromParcel = RadioMetadata.CREATOR.createFromParcel(parcel);
- assertWithMessage("Radio metadata created from parcel")
+ mExpect.withMessage("Radio metadata created from parcel")
.that(metadataFromParcel).isEqualTo(metadataExpected);
}
@@ -441,7 +498,43 @@ public final class RadioMetadataTest {
parcel.setDataPosition(0);
RadioMetadata metadataFromParcel = RadioMetadata.CREATOR.createFromParcel(parcel);
- assertWithMessage("Radio metadata created from parcel with string array type metadata")
+ mExpect.withMessage("Radio metadata created from parcel with string array type metadata")
.that(metadataFromParcel).isEqualTo(metadataExpected);
}
+
+ @Test
+ public void build_withRadioMetadataMeetingSizeRequirement() {
+ int maxBitmapSize = 10;
+ doReturn(maxBitmapSize - 1).when(mBitmapValue).getHeight();
+ doReturn(maxBitmapSize - 1).when(mBitmapValue).getWidth();
+ RadioMetadata metadataSource = mBuilder.putBitmap(
+ RadioMetadata.METADATA_KEY_ICON, mBitmapValue).build();
+
+ RadioMetadata metadata = new RadioMetadata.Builder(metadataSource, maxBitmapSize).build();
+
+ mExpect.withMessage("Bitmap without resizing")
+ .that(metadata.getBitmap(RadioMetadata.METADATA_KEY_ICON)).isEqualTo(mBitmapValue);
+ }
+
+ @Test
+ public void build_withRadioMetadataAboveSizeRequirement() {
+ int maxBitmapSize = 10;
+ int bitmapHeight = 10;
+ int bitmapWidth = 20;
+ int bitmapWidthResized = maxBitmapSize;
+ int bitmapHeightResized = (bitmapHeight * bitmapWidthResized) / bitmapWidth;
+ Bitmap bitmapResized = mock(Bitmap.class);
+ doReturn(bitmapHeight).when(mBitmapValue).getHeight();
+ doReturn(bitmapWidth).when(mBitmapValue).getWidth();
+ doReturn(bitmapResized).when(() -> Bitmap.createScaledBitmap(
+ mBitmapValue, bitmapWidthResized, bitmapHeightResized, /* filter= */ true));
+ RadioMetadata metadataSource = mBuilder.putBitmap(
+ RadioMetadata.METADATA_KEY_ICON, mBitmapValue).build();
+
+ RadioMetadata metadata = new RadioMetadata.Builder(metadataSource, maxBitmapSize).build();
+
+ mExpect.withMessage("Bitmap with resizing")
+ .that(metadata.getBitmap(RadioMetadata.METADATA_KEY_ICON))
+ .isEqualTo(bitmapResized);
+ }
}
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java
index b36367b91803..e68a0b8225e6 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/UniqueProgramIdentifierTest.java
@@ -16,6 +16,8 @@
package android.hardware.radio;
+import static org.junit.Assert.assertThrows;
+
import android.annotation.Nullable;
import android.os.Parcel;
@@ -45,6 +47,24 @@ public final class UniqueProgramIdentifierTest {
public final Expect expect = Expect.create();
@Test
+ public void requireCriticalSecondaryIds_forDab() {
+ expect.withMessage("Critical secondary Id required for DAB")
+ .that(UniqueProgramIdentifier.requireCriticalSecondaryIds(
+ ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT)).isTrue();
+ }
+
+ @Test
+ public void constructor_withNullSelector() {
+ ProgramSelector nullSelector = null;
+
+ NullPointerException thrown = assertThrows(NullPointerException.class,
+ () -> new UniqueProgramIdentifier(nullSelector));
+
+ expect.withMessage("Null pointer exception for unique program identifier")
+ .that(thrown).hasMessageThat().contains("can not be null");
+ }
+
+ @Test
public void getPrimaryId_forUniqueProgramIdentifier() {
ProgramSelector dabSelector = getDabSelector(new ProgramSelector.Identifier[]{
DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
@@ -67,6 +87,27 @@ public final class UniqueProgramIdentifierTest {
}
@Test
+ public void getCriticalSecondaryIds_forDabUniqueProgramIdentifierWithoutEnsemble() {
+ ProgramSelector dabSelector = getDabSelector(new ProgramSelector.Identifier[]{
+ DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
+ UniqueProgramIdentifier dabIdentifier = new UniqueProgramIdentifier(dabSelector);
+
+ expect.withMessage("Critical secondary ids of DAB unique identifier without ensemble")
+ .that(dabIdentifier.getCriticalSecondaryIds())
+ .containsExactly(DAB_FREQUENCY_IDENTIFIER);
+ }
+
+ @Test
+ public void getCriticalSecondaryIds_forDabUniqueProgramIdentifierWithoutSecondaryIds() {
+ ProgramSelector dabSelector = getDabSelector(new ProgramSelector.Identifier[]{},
+ /* vendorIds= */ null);
+ UniqueProgramIdentifier dabIdentifier = new UniqueProgramIdentifier(dabSelector);
+
+ expect.withMessage("Critical secondary ids of DAB unique identifier")
+ .that(dabIdentifier.getCriticalSecondaryIds()).isEmpty();
+ }
+
+ @Test
public void getCriticalSecondaryIds_forFmUniqueProgramIdentifier() {
UniqueProgramIdentifier fmUniqueIdentifier = new UniqueProgramIdentifier(
new ProgramSelector(ProgramSelector.PROGRAM_TYPE_FM, FM_IDENTIFIER,
@@ -147,6 +188,19 @@ public final class UniqueProgramIdentifierTest {
}
@Test
+ public void equals_withMissingSecondaryIdsForUniqueProgramIdentifier() {
+ ProgramSelector dabSelector1 = getDabSelector(new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER}, /* vendorIds= */ null);
+ UniqueProgramIdentifier dabIdentifier1 = new UniqueProgramIdentifier(dabSelector1);
+ ProgramSelector dabSelector2 = getDabSelector(new ProgramSelector.Identifier[]{
+ DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER}, /* vendorIds= */ null);
+ UniqueProgramIdentifier dabIdentifier2 = new UniqueProgramIdentifier(dabSelector2);
+
+ expect.withMessage("DAB unique identifier with missing secondary ids")
+ .that(dabIdentifier1).isNotEqualTo(dabIdentifier2);
+ }
+
+ @Test
public void describeContents_forUniqueProgramIdentifier() {
UniqueProgramIdentifier fmUniqueIdentifier = new UniqueProgramIdentifier(FM_IDENTIFIER);
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index e18de2e66399..d1a90aea835c 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -62,6 +62,7 @@ android_test {
"frameworks-core-util-lib",
"mockwebserver",
"guava",
+ "android.app.usage.flags-aconfig-java",
"android.view.accessibility.flags-aconfig-java",
"androidx.core_core",
"androidx.core_core-ktx",
diff --git a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
index 9d85b65d4dac..1925588e8904 100644
--- a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
+++ b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
@@ -16,8 +16,6 @@
package android.app;
-import static com.google.common.truth.Truth.assertThat;
-
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.fail;
@@ -28,8 +26,6 @@ import android.net.Uri;
import android.os.Parcel;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
-import android.service.notification.ZenDeviceEffects;
-import android.service.notification.ZenPolicy;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -230,66 +226,4 @@ public class AutomaticZenRuleTest {
assertThrows(IllegalArgumentException.class, () -> builder.setType(100));
}
-
- @Test
- @EnableFlags(Flags.FLAG_MODES_API)
- public void testCanUpdate_nullPolicyAndDeviceEffects() {
- AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name",
- Uri.parse("uri://short"));
-
- AutomaticZenRule rule = builder.setUserModifiedFields(0)
- .setZenPolicy(null)
- .setDeviceEffects(null)
- .build();
-
- assertThat(rule.canUpdate()).isTrue();
-
- rule = builder.setUserModifiedFields(1).build();
- assertThat(rule.canUpdate()).isFalse();
- }
-
- @Test
- @EnableFlags(Flags.FLAG_MODES_API)
- public void testCanUpdate_policyModified() {
- ZenPolicy.Builder policyBuilder = new ZenPolicy.Builder().setUserModifiedFields(0);
- ZenPolicy policy = policyBuilder.build();
-
- AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name",
- Uri.parse("uri://short"));
- AutomaticZenRule rule = builder.setUserModifiedFields(0)
- .setZenPolicy(policy)
- .setDeviceEffects(null).build();
-
- // Newly created ZenPolicy is not user modified.
- assertThat(policy.getUserModifiedFields()).isEqualTo(0);
- assertThat(rule.canUpdate()).isTrue();
-
- policy = policyBuilder.setUserModifiedFields(1).build();
- assertThat(policy.getUserModifiedFields()).isEqualTo(1);
- rule = builder.setZenPolicy(policy).build();
- assertThat(rule.canUpdate()).isFalse();
- }
-
- @Test
- @EnableFlags(Flags.FLAG_MODES_API)
- public void testCanUpdate_deviceEffectsModified() {
- ZenDeviceEffects.Builder deviceEffectsBuilder =
- new ZenDeviceEffects.Builder().setUserModifiedFields(0);
- ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
-
- AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("name",
- Uri.parse("uri://short"));
- AutomaticZenRule rule = builder.setUserModifiedFields(0)
- .setZenPolicy(null)
- .setDeviceEffects(deviceEffects).build();
-
- // Newly created ZenDeviceEffects is not user modified.
- assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(0);
- assertThat(rule.canUpdate()).isTrue();
-
- deviceEffects = deviceEffectsBuilder.setUserModifiedFields(1).build();
- assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(1);
- rule = builder.setDeviceEffects(deviceEffects).build();
- assertThat(rule.canUpdate()).isFalse();
- }
}
diff --git a/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java b/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java
index 839b645cf352..4565978434ee 100644
--- a/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java
+++ b/core/tests/coretests/src/android/app/usage/UsageEventsQueryTest.java
@@ -15,24 +15,34 @@
*/
package android.app.usage;
+import static android.app.usage.Flags.FLAG_FILTER_BASED_EVENT_QUERY_API;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import android.app.usage.UsageEvents.Event;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Random;
-import java.util.Set;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class UsageEventsQueryTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
@Test
+ @RequiresFlagsEnabled(FLAG_FILTER_BASED_EVENT_QUERY_API)
public void testQueryDuration() {
// Test with negative beginTimeMillis.
long beginTimeMillis = -100;
@@ -97,6 +107,7 @@ public class UsageEventsQueryTest {
}
@Test
+ @RequiresFlagsEnabled(FLAG_FILTER_BASED_EVENT_QUERY_API)
public void testQueryEventTypes() {
Random rnd = new Random();
UsageEventsQuery.Builder queryBuilder = new UsageEventsQuery.Builder(1000, 2000);
@@ -104,7 +115,7 @@ public class UsageEventsQueryTest {
// Test with invalid event type.
int eventType = Event.NONE - 1;
try {
- queryBuilder.addEventTypes(eventType);
+ queryBuilder.setEventTypes(eventType);
fail("Invalid event type: " + eventType);
} catch (IllegalArgumentException e) {
// Expected, fall through.
@@ -112,7 +123,7 @@ public class UsageEventsQueryTest {
eventType = Event.MAX_EVENT_TYPE + 1;
try {
- queryBuilder.addEventTypes(eventType);
+ queryBuilder.setEventTypes(eventType);
fail("Invalid event type: " + eventType);
} catch (IllegalArgumentException e) {
// Expected, fall through.
@@ -121,11 +132,11 @@ public class UsageEventsQueryTest {
// Test with valid and duplicate event types.
eventType = rnd.nextInt(Event.MAX_EVENT_TYPE + 1);
try {
- UsageEventsQuery query = queryBuilder.addEventTypes(eventType, eventType, eventType)
+ UsageEventsQuery query = queryBuilder.setEventTypes(eventType, eventType, eventType)
.build();
- Set<Integer> eventTypeSet = query.getEventTypes();
- assertEquals(eventTypeSet.size(), 1);
- int type = eventTypeSet.iterator().next();
+ final int[] eventTypesArray = query.getEventTypes();
+ assertEquals(eventTypesArray.length, 1);
+ int type = eventTypesArray[0];
assertEquals(type, eventType);
} catch (IllegalArgumentException e) {
fail("Valid event type: " + eventType);
diff --git a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
index 1617eda6a77c..e32a57b1aefe 100644
--- a/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
+++ b/core/tests/coretests/src/android/content/res/FontScaleConverterFactoryTest.kt
@@ -48,7 +48,7 @@ class FontScaleConverterFactoryTest {
@get:Rule
val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
- private lateinit var defaultLookupTables: SparseArray<FontScaleConverter>
+ private var defaultLookupTables: SparseArray<FontScaleConverter>? = null
@Before
fun setup() {
@@ -58,7 +58,9 @@ class FontScaleConverterFactoryTest {
@After
fun teardown() {
// Restore the default tables (since some tests will have added extras to the cache)
- FontScaleConverterFactory.sLookupTables = defaultLookupTables
+ if (defaultLookupTables != null) {
+ FontScaleConverterFactory.sLookupTables = defaultLookupTables!!
+ }
}
@Test
diff --git a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
new file mode 100644
index 000000000000..27869bbf5392
--- /dev/null
+++ b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
@@ -0,0 +1,224 @@
+/*
+ * 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.text
+
+import android.graphics.Paint
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+const val LEFT_EDGE = Paint.TEXT_RUN_FLAG_LEFT_EDGE
+const val RIGHT_EDGE = Paint.TEXT_RUN_FLAG_RIGHT_EDGE
+const val MIDDLE_OF_LINE = 0
+const val WHOLE_LINE = LEFT_EDGE or RIGHT_EDGE
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TextLineLetterSpacingTest {
+
+ @Rule
+ @JvmField
+ val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @Test
+ fun calculateRunFlagTest() {
+ // Only one Bidi run
+ assertThat(TextLine.calculateRunFlag(0, 1, Layout.DIR_LEFT_TO_RIGHT))
+ .isEqualTo(WHOLE_LINE)
+ assertThat(TextLine.calculateRunFlag(0, 1, Layout.DIR_RIGHT_TO_LEFT))
+ .isEqualTo(WHOLE_LINE)
+
+ // Two BiDi Runs.
+ // If the layout is LTR, the first run is the left most run.
+ assertThat(TextLine.calculateRunFlag(0, 2, Layout.DIR_LEFT_TO_RIGHT))
+ .isEqualTo(LEFT_EDGE)
+ // If the layout is LTR, the last run is the right most run.
+ assertThat(TextLine.calculateRunFlag(1, 2, Layout.DIR_LEFT_TO_RIGHT))
+ .isEqualTo(RIGHT_EDGE)
+ // If the layout is RTL, the first run is the right most run.
+ assertThat(TextLine.calculateRunFlag(0, 2, Layout.DIR_RIGHT_TO_LEFT))
+ .isEqualTo(RIGHT_EDGE)
+ // If the layout is RTL, the last run is the left most run.
+ assertThat(TextLine.calculateRunFlag(1, 2, Layout.DIR_RIGHT_TO_LEFT))
+ .isEqualTo(LEFT_EDGE)
+
+ // Three BiDi Runs.
+ // If the layout is LTR, the first run is the left most run.
+ assertThat(TextLine.calculateRunFlag(0, 3, Layout.DIR_LEFT_TO_RIGHT))
+ .isEqualTo(LEFT_EDGE)
+ // Regardless of the context direction, the middle run must not have any flags.
+ assertThat(TextLine.calculateRunFlag(1, 3, Layout.DIR_LEFT_TO_RIGHT))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // If the layout is LTR, the last run is the right most run.
+ assertThat(TextLine.calculateRunFlag(2, 3, Layout.DIR_LEFT_TO_RIGHT))
+ .isEqualTo(RIGHT_EDGE)
+ // If the layout is RTL, the first run is the right most run.
+ assertThat(TextLine.calculateRunFlag(0, 3, Layout.DIR_RIGHT_TO_LEFT))
+ .isEqualTo(RIGHT_EDGE)
+ // Regardless of the context direction, the middle run must not have any flags.
+ assertThat(TextLine.calculateRunFlag(1, 3, Layout.DIR_RIGHT_TO_LEFT))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // If the layout is RTL, the last run is the left most run.
+ assertThat(TextLine.calculateRunFlag(2, 3, Layout.DIR_RIGHT_TO_LEFT))
+ .isEqualTo(LEFT_EDGE)
+ }
+
+ @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ @Test
+ fun resolveRunFlagForSubSequenceTest() {
+ val runStart = 5
+ val runEnd = 15
+ // Regardless of the run directions, if the span covers entire Bidi run, the same runFlag
+ // should be returned.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, false, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(LEFT_EDGE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, true, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(LEFT_EDGE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, false, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(RIGHT_EDGE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, true, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(RIGHT_EDGE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, false, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(WHOLE_LINE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, true, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(WHOLE_LINE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, false, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(MIDDLE_OF_LINE)
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, true, runStart, runEnd, runStart, runEnd))
+ .isEqualTo(MIDDLE_OF_LINE)
+
+
+
+ // Left edge of LTR text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, false, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(LEFT_EDGE)
+ // Left edge of RTL text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, true, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Right edge of LTR text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, false, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Right edge of RTL text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, true, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(RIGHT_EDGE)
+ // Whole line of LTR text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, false, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(LEFT_EDGE)
+ // Whole line of RTL text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, true, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(RIGHT_EDGE)
+ // Middle of LTR text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, false, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Middle of RTL text, span start from run start offset but not cover the run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, true, runStart, runEnd, runStart, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+
+
+
+ // Left edge of LTR text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, false, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Left edge of RTL text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, true, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(LEFT_EDGE)
+ // Right edge of LTR text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, false, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(RIGHT_EDGE)
+ // Right edge of RTL text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, true, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Whole line of LTR text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, false, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(RIGHT_EDGE)
+ // Whole line of RTL text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, true, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(LEFT_EDGE)
+ // Middle of LTR text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, false, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Middle of RTL text, span start from middle of run start offset until end of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd))
+ .isEqualTo(MIDDLE_OF_LINE)
+
+
+
+ // Left edge of LTR text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Left edge of RTL text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ LEFT_EDGE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Right edge of LTR text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Right edge of RTL text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ RIGHT_EDGE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Whole line of LTR text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Whole line of RTL text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ WHOLE_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Middle of LTR text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ // Middle of RTL text, span start from middle of run.
+ assertThat(TextLine.resolveRunFlagForSubSequence(
+ MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+ .isEqualTo(MIDDLE_OF_LINE)
+ }
+} \ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index cf3eb12498ca..cfbda84f24e1 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -19,6 +19,7 @@ package android.view;
import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
@@ -575,8 +576,13 @@ public class ViewRootImplTest {
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+ FRAME_RATE_CATEGORY_HIGH_HINT);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+ viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
+ assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
index 0742052cce53..ec4c563e469e 100644
--- a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
@@ -18,39 +18,67 @@ package com.android.internal.os;
import static com.google.common.truth.Truth.assertThat;
-import android.util.Xml;
-
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileWriter;
import java.io.IOException;
+import java.nio.file.Files;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class MonotonicClockTest {
private final MockClock mClock = new MockClock();
+ private File mFile;
+
+ @Before
+ public void setup() throws IOException {
+ File systemDir = Files.createTempDirectory("MonotonicClockTest").toFile();
+ mFile = new File(systemDir, "test_monotonic_clock.xml");
+ if (mFile.exists()) {
+ assertThat(mFile.delete()).isTrue();
+ }
+ }
@Test
public void persistence() throws IOException {
- MonotonicClock monotonicClock = new MonotonicClock(1000, mClock);
+ MonotonicClock monotonicClock = new MonotonicClock(mFile, 1000, mClock);
mClock.realtime = 234;
assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- monotonicClock.writeXml(out, Xml.newBinarySerializer());
+ monotonicClock.write();
mClock.realtime = 42;
- MonotonicClock newMonotonicClock = new MonotonicClock(0, mClock);
- ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
- newMonotonicClock.readXml(in, Xml.newBinaryPullParser());
+ MonotonicClock newMonotonicClock = new MonotonicClock(mFile, 0, mClock);
mClock.realtime = 2000;
assertThat(newMonotonicClock.monotonicTime()).isEqualTo(1234 - 42 + 2000);
}
+
+ @Test
+ public void constructor() {
+ MonotonicClock monotonicClock = new MonotonicClock(null, 1000, mClock);
+ mClock.realtime = 234;
+
+ assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
+ }
+
+ @Test
+ public void corruptedFile() throws IOException {
+ // Create an invalid binary XML file to cause IOException: "Unexpected magic number"
+ try (FileWriter w = new FileWriter(mFile)) {
+ w.write("garbage");
+ }
+
+ MonotonicClock monotonicClock = new MonotonicClock(mFile, 1000, mClock);
+ mClock.realtime = 234;
+
+ assertThat(monotonicClock.monotonicTime()).isEqualTo(1234);
+ }
}
diff --git a/data/etc/com.android.documentsui.xml b/data/etc/com.android.documentsui.xml
index d32cbecb16ec..2e521e30c673 100644
--- a/data/etc/com.android.documentsui.xml
+++ b/data/etc/com.android.documentsui.xml
@@ -23,5 +23,7 @@
<permission name="android.permission.MODIFY_QUIET_MODE"/>
<permission name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
<permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"/>
+ <!-- Permissions required for reading device configs -->
+ <permission name="android.permission.READ_DEVICE_CONFIG" />
</privapp-permissions>
</permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index a1ea2b8ce032..f7c278c76838 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -426,6 +426,7 @@ applications that come with the platform
<!-- Permissions required for CTS test - android.server.biometrics -->
<permission name="android.permission.USE_BIOMETRIC" />
<permission name="android.permission.TEST_BIOMETRIC" />
+ <permission name="android.permission.MANAGE_BIOMETRIC_DIALOG" />
<!-- Permissions required for CTS test - CtsContactsProviderTestCases -->
<permission name="android.contacts.permission.MANAGE_SIM_ACCOUNTS" />
<!-- Permissions required for CTS test - CtsHdmiCecHostTestCases -->
@@ -511,6 +512,7 @@ applications that come with the platform
<permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
<!-- Permission required for CTS test - CtsTelephonyTestCases -->
<permission name="android.permission.BIND_TELECOM_CONNECTION_SERVICE" />
+ <permission name="android.permission.ACCESS_LAST_KNOWN_CELL_ID" />
<!-- Permission required for CTS test - CtsAppTestCases -->
<permission name="android.permission.CAPTURE_MEDIA_OUTPUT" />
<permission name="android.permission.CAPTURE_TUNER_AUDIO_INPUT" />
@@ -541,6 +543,8 @@ applications that come with the platform
<permission name="android.permission.GET_BINDING_UID_IMPORTANCE"/>
<!-- Permission required for CTS test NotificationManagerZenTest -->
<permission name="android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS" />
+ <!-- Permission required for BinaryTransparencyService shell API and host test -->
+ <permission name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
</privapp-permissions>
<privapp-permissions package="com.android.statementservice">
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 917a30061aca..3a778c314606 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -535,6 +535,12 @@
"group": "WM_DEBUG_TASKS",
"at": "com\/android\/server\/wm\/ActivityStarter.java"
},
+ "-1583619037": {
+ "message": "Failed to register MediaProjectionWatcherCallback",
+ "level": "ERROR",
+ "group": "WM_ERROR",
+ "at": "com\/android\/server\/wm\/ScreenRecordingCallbackController.java"
+ },
"-1582845629": {
"message": "Starting animation on %s",
"level": "VERBOSE",
@@ -2983,12 +2989,6 @@
"group": "WM_DEBUG_SCREEN_ON",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "466506262": {
- "message": "Clear freezing of %s: visible=%b freezing=%b",
- "level": "VERBOSE",
- "group": "WM_DEBUG_ORIENTATION",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"485170982": {
"message": "Not finishing noHistory %s on stop because we're just sleeping",
"level": "DEBUG",
diff --git a/data/fonts/Android.bp b/data/fonts/Android.bp
index 3dd9ba9db1d9..471acaa0b1b6 100644
--- a/data/fonts/Android.bp
+++ b/data/fonts/Android.bp
@@ -48,12 +48,35 @@ prebuilt_font {
// Copies the font configuration file into system/etc for the product as fonts.xml.
// Additional fonts should be installed to /product/fonts/ alongside a corresponding
// fonts_customiztion.xml in /product/etc/
-prebuilt_etc {
+
+soong_config_bool_variable {
+ name: "use_var_font",
+}
+
+soong_config_module_type {
+ name: "prebuilt_fonts_xml",
+ module_type: "prebuilt_etc",
+ config_namespace: "noto_sans_cjk_config",
+ bool_variables: ["use_var_font"],
+ properties: ["src"],
+}
+
+prebuilt_fonts_xml {
name: "fonts.xml",
src: "fonts.xml",
+ soong_config_variables: {
+ use_var_font: {
+ src: "fonts_cjkvf.xml",
+ },
+ },
}
-prebuilt_etc {
+prebuilt_fonts_xml {
name: "font_fallback.xml",
src: "font_fallback.xml",
+ soong_config_variables: {
+ use_var_font: {
+ src: "font_fallback_cjkvf.xml",
+ },
+ },
}
diff --git a/data/fonts/font_fallback_cjkvf.xml b/data/fonts/font_fallback_cjkvf.xml
new file mode 100644
index 000000000000..70474bae11e9
--- /dev/null
+++ b/data/fonts/font_fallback_cjkvf.xml
@@ -0,0 +1,1015 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ In this file, all fonts without names are added to the default list.
+ Fonts are chosen based on a match: full BCP-47 language tag including
+ script, then just language, and finally order (the first font containing
+ the glyph).
+
+ Order of appearance is also the tiebreaker for weight matching. This is
+ the reason why the 900 weights of Roboto precede the 700 weights - we
+ prefer the former when an 800 weight is requested. Since bold spans
+ effectively add 300 to the weight, this ensures that 900 is the bold
+ paired with the 500 weight, ensuring adequate contrast.
+
+ TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
+-->
+<familyset version="23">
+ <!-- first font is default -->
+ <family name="sans-serif" varFamilyType="2">
+ <font>Roboto-Regular.ttf
+ <axis tag="wdth" stylevalue="100" />
+ </font>
+ </family>
+
+
+ <!-- Note that aliases must come after the fonts they reference. -->
+ <alias name="sans-serif-thin" to="sans-serif" weight="100" />
+ <alias name="sans-serif-light" to="sans-serif" weight="300" />
+ <alias name="sans-serif-medium" to="sans-serif" weight="500" />
+ <alias name="sans-serif-black" to="sans-serif" weight="900" />
+ <alias name="arial" to="sans-serif" />
+ <alias name="helvetica" to="sans-serif" />
+ <alias name="tahoma" to="sans-serif" />
+ <alias name="verdana" to="sans-serif" />
+
+ <family name="sans-serif-condensed" varFamilyType="2">
+ <font>Roboto-Regular.ttf
+ <axis tag="wdth" stylevalue="75" />
+ </font>
+ </family>
+ <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
+ <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
+
+ <family name="serif">
+ <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font>
+ <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
+ <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
+ <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
+ </family>
+ <alias name="serif-bold" to="serif" weight="700" />
+ <alias name="times" to="serif" />
+ <alias name="times new roman" to="serif" />
+ <alias name="palatino" to="serif" />
+ <alias name="georgia" to="serif" />
+ <alias name="baskerville" to="serif" />
+ <alias name="goudy" to="serif" />
+ <alias name="fantasy" to="serif" />
+ <alias name="ITC Stone Serif" to="serif" />
+
+ <family name="monospace">
+ <font weight="400" style="normal">DroidSansMono.ttf</font>
+ </family>
+ <alias name="sans-serif-monospace" to="monospace" />
+ <alias name="monaco" to="monospace" />
+
+ <family name="serif-monospace">
+ <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font>
+ </family>
+ <alias name="courier" to="serif-monospace" />
+ <alias name="courier new" to="serif-monospace" />
+
+ <family name="casual">
+ <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
+ </family>
+
+ <family name="cursive" varFamilyType="1">
+ <font>DancingScript-Regular.ttf</font>
+ </family>
+
+ <family name="sans-serif-smallcaps">
+ <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
+ </family>
+
+ <family name="source-sans-pro">
+ <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
+ <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
+ <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
+ <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
+ <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
+ <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
+ </family>
+ <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
+
+ <family name="roboto-flex" varFamilyType="2">
+ <font>RobotoFlex-Regular.ttf
+ <axis tag="wdth" stylevalue="100" />
+ </font>
+ </family>
+
+ <!-- fallback fonts -->
+ <family lang="und-Arab" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
+ NotoNaskhArabic-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
+ </family>
+ <family lang="und-Arab" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
+ NotoNaskhArabicUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Ethi" varFamilyType="1">
+ <font postScriptName="NotoSansEthiopic-Regular">
+ NotoSansEthiopic-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifEthiopic-Regular">
+ NotoSerifEthiopic-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Hebr">
+ <font weight="400" style="normal" postScriptName="NotoSansHebrew">
+ NotoSansHebrew-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
+ </family>
+ <family lang="und-Thai" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">
+ NotoSerifThai-Regular.ttf
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
+ </family>
+ <family lang="und-Thai" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansThaiUI">
+ NotoSansThaiUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Armn" varFamilyType="1">
+ <font postScriptName="NotoSansArmenian-Regular">
+ NotoSansArmenian-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifArmenian-Regular">
+ NotoSerifArmenian-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Geor,und-Geok" varFamilyType="1">
+ <font postScriptName="NotoSansGeorgian-Regular">
+ NotoSansGeorgian-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifGeorgian-Regular">
+ NotoSerifGeorgian-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Deva" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansDevanagari-Regular">
+ NotoSansDevanagari-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifDevanagari-Regular">
+ NotoSerifDevanagari-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Deva" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansDevanagariUI-Regular">
+ NotoSansDevanagariUI-VF.ttf
+ </font>
+ </family>
+
+ <!-- All scripts of India should come after Devanagari, due to shared
+ danda characters.
+ -->
+ <family lang="und-Gujr" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansGujarati">
+ NotoSansGujarati-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Gujr" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI">
+ NotoSansGujaratiUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Guru" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansGurmukhi-Regular">
+ NotoSansGurmukhi-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifGurmukhi-Regular">
+ NotoSerifGurmukhi-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Guru" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansGurmukhiUI-Regular">
+ NotoSansGurmukhiUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Taml" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansTamil-Regular">
+ NotoSansTamil-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifTamil-Regular">
+ NotoSerifTamil-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Taml" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansTamilUI-Regular">
+ NotoSansTamilUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Mlym" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansMalayalam-Regular">
+ NotoSansMalayalam-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifMalayalam-Regular">
+ NotoSerifMalayalam-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Mlym" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansMalayalamUI-Regular">
+ NotoSansMalayalamUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Beng" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansBengali-Regular">
+ NotoSansBengali-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifBengali-Regular">
+ NotoSerifBengali-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Beng" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansBengaliUI-Regular">
+ NotoSansBengaliUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Telu" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansTelugu-Regular">
+ NotoSansTelugu-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifTelugu-Regular">
+ NotoSerifTelugu-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Telu" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansTeluguUI-Regular">
+ NotoSansTeluguUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Knda" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansKannada-Regular">
+ NotoSansKannada-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifKannada-Regular">
+ NotoSerifKannada-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Knda" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansKannadaUI-Regular">
+ NotoSansKannadaUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Orya" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font>
+ </family>
+ <family lang="und-Orya" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansOriyaUI">
+ NotoSansOriyaUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Sinh" variant="elegant" varFamilyType="1">
+ <font postScriptName="NotoSansSinhala-Regular">
+ NotoSansSinhala-VF.ttf
+ </font>
+ <font fallbackFor="serif" postScriptName="NotoSerifSinhala-Regular">
+ NotoSerifSinhala-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Sinh" variant="compact" varFamilyType="1">
+ <font postScriptName="NotoSansSinhalaUI-Regular">
+ NotoSansSinhalaUI-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Khmr" variant="elegant">
+ <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="26.0"/>
+ </font>
+ <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="39.0"/>
+ </font>
+ <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="58.0"/>
+ </font>
+ <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="90.0"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="108.0"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="128.0"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="151.0"/>
+ </font>
+ <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="169.0"/>
+ </font>
+ <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="190.0"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font>
+ </family>
+ <family lang="und-Khmr" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansKhmerUI">
+ NotoSansKhmerUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Laoo" variant="elegant">
+ <font weight="400" style="normal">NotoSansLao-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">
+ NotoSerifLao-Regular.ttf
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
+ </family>
+ <family lang="und-Laoo" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Mymr" variant="elegant">
+ <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
+ <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
+ <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
+ </family>
+ <family lang="und-Mymr" variant="compact">
+ <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
+ <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
+ <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
+ </family>
+ <family lang="und-Thaa">
+ <font weight="400" style="normal" postScriptName="NotoSansThaana">
+ NotoSansThaana-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
+ </family>
+ <family lang="und-Cham">
+ <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
+ </family>
+ <family lang="und-Ahom">
+ <font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
+ </family>
+ <family lang="und-Adlm" varFamilyType="1">
+ <font postScriptName="NotoSansAdlam-Regular">
+ NotoSansAdlam-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Avst">
+ <font weight="400" style="normal" postScriptName="NotoSansAvestan">
+ NotoSansAvestan-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bali">
+ <font weight="400" style="normal" postScriptName="NotoSansBalinese">
+ NotoSansBalinese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bamu">
+ <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Batk">
+ <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Brah">
+ <font weight="400" style="normal" postScriptName="NotoSansBrahmi">
+ NotoSansBrahmi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bugi">
+ <font weight="400" style="normal" postScriptName="NotoSansBuginese">
+ NotoSansBuginese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Buhd">
+ <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cans">
+ <font weight="400" style="normal">
+ NotoSansCanadianAboriginal-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cari">
+ <font weight="400" style="normal" postScriptName="NotoSansCarian">
+ NotoSansCarian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cakm">
+ <font weight="400" style="normal">NotoSansChakma-Regular.otf</font>
+ </family>
+ <family lang="und-Cher">
+ <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
+ </family>
+ <family lang="und-Copt">
+ <font weight="400" style="normal" postScriptName="NotoSansCoptic">
+ NotoSansCoptic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Xsux">
+ <font weight="400" style="normal" postScriptName="NotoSansCuneiform">
+ NotoSansCuneiform-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cprt">
+ <font weight="400" style="normal" postScriptName="NotoSansCypriot">
+ NotoSansCypriot-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Dsrt">
+ <font weight="400" style="normal" postScriptName="NotoSansDeseret">
+ NotoSansDeseret-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Egyp">
+ <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs">
+ NotoSansEgyptianHieroglyphs-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Elba">
+ <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font>
+ </family>
+ <family lang="und-Glag">
+ <font weight="400" style="normal" postScriptName="NotoSansGlagolitic">
+ NotoSansGlagolitic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Goth">
+ <font weight="400" style="normal" postScriptName="NotoSansGothic">
+ NotoSansGothic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Hano">
+ <font weight="400" style="normal" postScriptName="NotoSansHanunoo">
+ NotoSansHanunoo-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Armi">
+ <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic">
+ NotoSansImperialAramaic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phli">
+ <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi">
+ NotoSansInscriptionalPahlavi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Prti">
+ <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian">
+ NotoSansInscriptionalParthian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Java">
+ <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font>
+ </family>
+ <family lang="und-Kthi">
+ <font weight="400" style="normal" postScriptName="NotoSansKaithi">
+ NotoSansKaithi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Kali">
+ <font weight="400" style="normal" postScriptName="NotoSansKayahLi">
+ NotoSansKayahLi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Khar">
+ <font weight="400" style="normal" postScriptName="NotoSansKharoshthi">
+ NotoSansKharoshthi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lepc">
+ <font weight="400" style="normal" postScriptName="NotoSansLepcha">
+ NotoSansLepcha-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Limb">
+ <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Linb">
+ <font weight="400" style="normal" postScriptName="NotoSansLinearB">
+ NotoSansLinearB-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lisu">
+ <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lyci">
+ <font weight="400" style="normal" postScriptName="NotoSansLycian">
+ NotoSansLycian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lydi">
+ <font weight="400" style="normal" postScriptName="NotoSansLydian">
+ NotoSansLydian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Mand">
+ <font weight="400" style="normal" postScriptName="NotoSansMandaic">
+ NotoSansMandaic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Mtei">
+ <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek">
+ NotoSansMeeteiMayek-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Talu">
+ <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue">
+ NotoSansNewTaiLue-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Nkoo">
+ <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Ogam">
+ <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Olck">
+ <font weight="400" style="normal" postScriptName="NotoSansOlChiki">
+ NotoSansOlChiki-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Ital">
+ <font weight="400" style="normal" postScriptName="NotoSansOldItalic">
+ NotoSansOldItalic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Xpeo">
+ <font weight="400" style="normal" postScriptName="NotoSansOldPersian">
+ NotoSansOldPersian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sarb">
+ <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian">
+ NotoSansOldSouthArabian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Orkh">
+ <font weight="400" style="normal" postScriptName="NotoSansOldTurkic">
+ NotoSansOldTurkic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Osge">
+ <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font>
+ </family>
+ <family lang="und-Osma">
+ <font weight="400" style="normal" postScriptName="NotoSansOsmanya">
+ NotoSansOsmanya-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phnx">
+ <font weight="400" style="normal" postScriptName="NotoSansPhoenician">
+ NotoSansPhoenician-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Rjng">
+ <font weight="400" style="normal" postScriptName="NotoSansRejang">
+ NotoSansRejang-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Runr">
+ <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Samr">
+ <font weight="400" style="normal" postScriptName="NotoSansSamaritan">
+ NotoSansSamaritan-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Saur">
+ <font weight="400" style="normal" postScriptName="NotoSansSaurashtra">
+ NotoSansSaurashtra-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Shaw">
+ <font weight="400" style="normal" postScriptName="NotoSansShavian">
+ NotoSansShavian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sund">
+ <font weight="400" style="normal" postScriptName="NotoSansSundanese">
+ NotoSansSundanese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sylo">
+ <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri">
+ NotoSansSylotiNagri-Regular.ttf
+ </font>
+ </family>
+ <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. -->
+ <family lang="und-Syre">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela">
+ NotoSansSyriacEstrangela-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Syrn">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern">
+ NotoSansSyriacEastern-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Syrj">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern">
+ NotoSansSyriacWestern-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tglg">
+ <font weight="400" style="normal" postScriptName="NotoSansTagalog">
+ NotoSansTagalog-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tagb">
+ <font weight="400" style="normal" postScriptName="NotoSansTagbanwa">
+ NotoSansTagbanwa-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lana">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiTham">
+ NotoSansTaiTham-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tavt">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiViet">
+ NotoSansTaiViet-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tibt" varFamilyType="1">
+ <font postScriptName="NotoSerifTibetan-Regular">
+ NotoSerifTibetan-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Tfng">
+ <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font>
+ </family>
+ <family lang="und-Ugar">
+ <font weight="400" style="normal" postScriptName="NotoSansUgaritic">
+ NotoSansUgaritic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Vaii">
+ <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf
+ </font>
+ </family>
+ <family>
+ <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
+ </family>
+ <family lang="zh-Hans">
+ <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="2" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="zh-Hant,zh-Bopo">
+ <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="3" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="ja">
+ <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="0" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="ko">
+ <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="1" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="und-Zsye">
+ <font weight="400" style="normal">NotoColorEmoji.ttf</font>
+ </family>
+ <family lang="und-Zsye">
+ <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font>
+ </family>
+ <family lang="und-Zsym">
+ <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font>
+ </family>
+ <!--
+ Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't
+ override the East Asian punctuation for Chinese.
+ -->
+ <family lang="und-Tale">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Yiii">
+ <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font>
+ </family>
+ <family lang="und-Mong">
+ <font weight="400" style="normal" postScriptName="NotoSansMongolian">
+ NotoSansMongolian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phag">
+ <font weight="400" style="normal" postScriptName="NotoSansPhagsPa">
+ NotoSansPhagsPa-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Hluw">
+ <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font>
+ </family>
+ <family lang="und-Bass">
+ <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font>
+ </family>
+ <family lang="und-Bhks">
+ <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font>
+ </family>
+ <family lang="und-Hatr">
+ <font weight="400" style="normal">NotoSansHatran-Regular.otf</font>
+ </family>
+ <family lang="und-Lina">
+ <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font>
+ </family>
+ <family lang="und-Mani">
+ <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font>
+ </family>
+ <family lang="und-Marc">
+ <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font>
+ </family>
+ <family lang="und-Merc">
+ <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font>
+ </family>
+ <family lang="und-Plrd">
+ <font weight="400" style="normal">NotoSansMiao-Regular.otf</font>
+ </family>
+ <family lang="und-Mroo">
+ <font weight="400" style="normal">NotoSansMro-Regular.otf</font>
+ </family>
+ <family lang="und-Mult">
+ <font weight="400" style="normal">NotoSansMultani-Regular.otf</font>
+ </family>
+ <family lang="und-Nbat">
+ <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font>
+ </family>
+ <family lang="und-Newa">
+ <font weight="400" style="normal">NotoSansNewa-Regular.otf</font>
+ </family>
+ <family lang="und-Narb">
+ <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font>
+ </family>
+ <family lang="und-Perm">
+ <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font>
+ </family>
+ <family lang="und-Hmng">
+ <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font>
+ </family>
+ <family lang="und-Palm">
+ <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font>
+ </family>
+ <family lang="und-Pauc">
+ <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font>
+ </family>
+ <family lang="und-Shrd">
+ <font weight="400" style="normal">NotoSansSharada-Regular.otf</font>
+ </family>
+ <family lang="und-Sora">
+ <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font>
+ </family>
+ <family lang="und-Gong">
+ <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font>
+ </family>
+ <family lang="und-Rohg">
+ <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font>
+ </family>
+ <family lang="und-Khoj">
+ <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font>
+ </family>
+ <family lang="und-Gonm">
+ <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font>
+ </family>
+ <family lang="und-Wcho">
+ <font weight="400" style="normal">NotoSansWancho-Regular.otf</font>
+ </family>
+ <family lang="und-Wara">
+ <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font>
+ </family>
+ <family lang="und-Gran">
+ <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font>
+ </family>
+ <family lang="und-Modi">
+ <font weight="400" style="normal">NotoSansModi-Regular.ttf</font>
+ </family>
+ <family lang="und-Dogr">
+ <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
+ </family>
+ <family lang="und-Medf" varFamilyType="1">
+ <font postScriptName="NotoSansMedefaidrin-Regular">
+ NotoSansMedefaidrin-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Soyo" varFamilyType="1">
+ <font postScriptName="NotoSansSoyombo-Regular">
+ NotoSansSoyombo-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Takr" varFamilyType="1">
+ <font postScriptName="NotoSansTakri-Regular">
+ NotoSansTakri-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Hmnp" varFamilyType="1">
+ <font postScriptName="NotoSerifHmongNyiakeng-Regular">
+ NotoSerifNyiakengPuachueHmong-VF.ttf
+ </font>
+ </family>
+ <family lang="und-Yezi" varFamilyType="1">
+ <font postScriptName="NotoSerifYezidi-Regular">
+ NotoSerifYezidi-VF.ttf
+ </font>
+ </family>
+</familyset>
diff --git a/data/fonts/fonts_cjkvf.xml b/data/fonts/fonts_cjkvf.xml
new file mode 100644
index 000000000000..8d91eddd807d
--- /dev/null
+++ b/data/fonts/fonts_cjkvf.xml
@@ -0,0 +1,1785 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ DEPRECATED: This XML file is no longer a source of the font files installed
+ in the system.
+
+ For the device vendors: please add your font configurations to the
+ platform/frameworks/base/data/font_fallback.xml and also add it to this XML
+ file as much as possible for apps that reads this XML file.
+
+ For the application developers: please stop reading this XML file and use
+ android.graphics.fonts.SystemFonts#getAvailableFonts Java API or
+ ASystemFontIterator_open NDK API for getting list of system installed
+ font files.
+
+ WARNING: Parsing of this file by third-party apps is not supported. The
+ file, and the font files it refers to, will be renamed and/or moved out
+ from their respective location in the next Android release, and/or the
+ format or syntax of the file may change significantly. If you parse this
+ file for information about system fonts, do it at your own risk. Your
+ application will almost certainly break with the next major Android
+ release.
+
+ In this file, all fonts without names are added to the default list.
+ Fonts are chosen based on a match: full BCP-47 language tag including
+ script, then just language, and finally order (the first font containing
+ the glyph).
+
+ Order of appearance is also the tiebreaker for weight matching. This is
+ the reason why the 900 weights of Roboto precede the 700 weights - we
+ prefer the former when an 800 weight is requested. Since bold spans
+ effectively add 300 to the weight, this ensures that 900 is the bold
+ paired with the 500 weight, ensuring adequate contrast.
+
+ TODO(rsheeter) update comment; ordering to match 800 to 900 is no longer required
+-->
+<familyset version="23">
+ <!-- first font is default -->
+ <family name="sans-serif">
+ <font weight="100" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ <font weight="100" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ </family>
+
+
+ <!-- Note that aliases must come after the fonts they reference. -->
+ <alias name="sans-serif-thin" to="sans-serif" weight="100" />
+ <alias name="sans-serif-light" to="sans-serif" weight="300" />
+ <alias name="sans-serif-medium" to="sans-serif" weight="500" />
+ <alias name="sans-serif-black" to="sans-serif" weight="900" />
+ <alias name="arial" to="sans-serif" />
+ <alias name="helvetica" to="sans-serif" />
+ <alias name="tahoma" to="sans-serif" />
+ <alias name="verdana" to="sans-serif" />
+
+ <family name="sans-serif-condensed">
+ <font weight="100" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="normal">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="0" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ <font weight="100" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="italic">Roboto-Regular.ttf
+ <axis tag="ital" stylevalue="1" />
+ <axis tag="wdth" stylevalue="75" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ </family>
+ <alias name="sans-serif-condensed-light" to="sans-serif-condensed" weight="300" />
+ <alias name="sans-serif-condensed-medium" to="sans-serif-condensed" weight="500" />
+
+ <family name="serif">
+ <font weight="400" style="normal" postScriptName="NotoSerif">NotoSerif-Regular.ttf</font>
+ <font weight="700" style="normal">NotoSerif-Bold.ttf</font>
+ <font weight="400" style="italic">NotoSerif-Italic.ttf</font>
+ <font weight="700" style="italic">NotoSerif-BoldItalic.ttf</font>
+ </family>
+ <alias name="serif-bold" to="serif" weight="700" />
+ <alias name="times" to="serif" />
+ <alias name="times new roman" to="serif" />
+ <alias name="palatino" to="serif" />
+ <alias name="georgia" to="serif" />
+ <alias name="baskerville" to="serif" />
+ <alias name="goudy" to="serif" />
+ <alias name="fantasy" to="serif" />
+ <alias name="ITC Stone Serif" to="serif" />
+
+ <family name="monospace">
+ <font weight="400" style="normal">DroidSansMono.ttf</font>
+ </family>
+ <alias name="sans-serif-monospace" to="monospace" />
+ <alias name="monaco" to="monospace" />
+
+ <family name="serif-monospace">
+ <font weight="400" style="normal" postScriptName="CutiveMono-Regular">CutiveMono.ttf</font>
+ </family>
+ <alias name="courier" to="serif-monospace" />
+ <alias name="courier new" to="serif-monospace" />
+
+ <family name="casual">
+ <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font>
+ </family>
+
+ <family name="cursive">
+ <font weight="400" style="normal">DancingScript-Regular.ttf
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="700" style="normal">DancingScript-Regular.ttf
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ </family>
+
+ <family name="sans-serif-smallcaps">
+ <font weight="400" style="normal">CarroisGothicSC-Regular.ttf</font>
+ </family>
+
+ <family name="source-sans-pro">
+ <font weight="400" style="normal">SourceSansPro-Regular.ttf</font>
+ <font weight="400" style="italic">SourceSansPro-Italic.ttf</font>
+ <font weight="600" style="normal">SourceSansPro-SemiBold.ttf</font>
+ <font weight="600" style="italic">SourceSansPro-SemiBoldItalic.ttf</font>
+ <font weight="700" style="normal">SourceSansPro-Bold.ttf</font>
+ <font weight="700" style="italic">SourceSansPro-BoldItalic.ttf</font>
+ </family>
+ <alias name="source-sans-pro-semi-bold" to="source-sans-pro" weight="600"/>
+
+ <family name="roboto-flex">
+ <font weight="100" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="normal">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="0" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ <font weight="100" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="100" />
+ </font>
+ <font weight="200" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="200" />
+ </font>
+ <font weight="300" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="300" />
+ </font>
+ <font weight="400" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="400" />
+ </font>
+ <font weight="500" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="500" />
+ </font>
+ <font weight="600" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="600" />
+ </font>
+ <font weight="700" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="700" />
+ </font>
+ <font weight="800" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="800" />
+ </font>
+ <font weight="900" style="italic">RobotoFlex-Regular.ttf
+ <axis tag="slnt" stylevalue="-10" />
+ <axis tag="wdth" stylevalue="100" />
+ <axis tag="wght" stylevalue="900" />
+ </font>
+ </family>
+
+ <!-- fallback fonts -->
+ <family lang="und-Arab" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoNaskhArabic">
+ NotoNaskhArabic-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoNaskhArabic-Bold.ttf</font>
+ </family>
+ <family lang="und-Arab" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoNaskhArabicUI">
+ NotoNaskhArabicUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoNaskhArabicUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Ethi">
+ <font weight="400" style="normal" postScriptName="NotoSansEthiopic-Regular">
+ NotoSansEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansEthiopic-Regular">
+ NotoSansEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansEthiopic-Regular">
+ NotoSansEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansEthiopic-Regular">
+ NotoSansEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifEthiopic-Regular">NotoSerifEthiopic-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Hebr">
+ <font weight="400" style="normal" postScriptName="NotoSansHebrew">
+ NotoSansHebrew-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansHebrew-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifHebrew-Regular.ttf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifHebrew-Bold.ttf</font>
+ </family>
+ <family lang="und-Thai" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansThai">NotoSansThai-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThai-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">
+ NotoSerifThai-Regular.ttf
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifThai-Bold.ttf</font>
+ </family>
+ <family lang="und-Thai" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansThaiUI">
+ NotoSansThaiUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThaiUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Armn">
+ <font weight="400" style="normal" postScriptName="NotoSansArmenian-Regular">
+ NotoSansArmenian-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansArmenian-Regular">
+ NotoSansArmenian-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansArmenian-Regular">
+ NotoSansArmenian-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansArmenian-Regular">
+ NotoSansArmenian-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifArmenian-Regular">NotoSerifArmenian-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Geor,und-Geok">
+ <font weight="400" style="normal" postScriptName="NotoSansGeorgian-Regular">
+ NotoSansGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansGeorgian-Regular">
+ NotoSansGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansGeorgian-Regular">
+ NotoSansGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansGeorgian-Regular">
+ NotoSansGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGeorgian-Regular">NotoSerifGeorgian-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Deva" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansDevanagari-Regular">
+ NotoSansDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansDevanagari-Regular">
+ NotoSansDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansDevanagari-Regular">
+ NotoSansDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansDevanagari-Regular">
+ NotoSansDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifDevanagari-Regular">NotoSerifDevanagari-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Deva" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+ NotoSansDevanagariUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+ NotoSansDevanagariUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+ NotoSansDevanagariUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansDevanagariUI-Regular">
+ NotoSansDevanagariUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+
+ <!-- All scripts of India should come after Devanagari, due to shared
+ danda characters.
+ -->
+ <family lang="und-Gujr" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansGujarati">
+ NotoSansGujarati-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansGujarati-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGujarati-Regular">NotoSerifGujarati-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Gujr" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansGujaratiUI">
+ NotoSansGujaratiUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansGujaratiUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Guru" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+ NotoSansGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+ NotoSansGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+ NotoSansGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansGurmukhi-Regular">
+ NotoSansGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifGurmukhi-Regular">NotoSerifGurmukhi-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Guru" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+ NotoSansGurmukhiUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+ NotoSansGurmukhiUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+ NotoSansGurmukhiUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansGurmukhiUI-Regular">
+ NotoSansGurmukhiUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Taml" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansTamil-Regular">
+ NotoSansTamil-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTamil-Regular">
+ NotoSansTamil-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansTamil-Regular">
+ NotoSansTamil-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansTamil-Regular">
+ NotoSansTamil-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTamil-Regular">NotoSerifTamil-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Taml" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansTamilUI-Regular">
+ NotoSansTamilUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTamilUI-Regular">
+ NotoSansTamilUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansTamilUI-Regular">
+ NotoSansTamilUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansTamilUI-Regular">
+ NotoSansTamilUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Mlym" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansMalayalam-Regular">
+ NotoSansMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansMalayalam-Regular">
+ NotoSansMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansMalayalam-Regular">
+ NotoSansMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansMalayalam-Regular">
+ NotoSansMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifMalayalam-Regular">NotoSerifMalayalam-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Mlym" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+ NotoSansMalayalamUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+ NotoSansMalayalamUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+ NotoSansMalayalamUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansMalayalamUI-Regular">
+ NotoSansMalayalamUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Beng" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansBengali-Regular">
+ NotoSansBengali-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansBengali-Regular">
+ NotoSansBengali-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansBengali-Regular">
+ NotoSansBengali-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansBengali-Regular">
+ NotoSansBengali-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifBengali-Regular">NotoSerifBengali-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Beng" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+ NotoSansBengaliUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+ NotoSansBengaliUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+ NotoSansBengaliUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansBengaliUI-Regular">
+ NotoSansBengaliUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Telu" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansTelugu-Regular">
+ NotoSansTelugu-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTelugu-Regular">
+ NotoSansTelugu-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansTelugu-Regular">
+ NotoSansTelugu-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansTelugu-Regular">
+ NotoSansTelugu-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifTelugu-Regular">NotoSerifTelugu-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Telu" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+ NotoSansTeluguUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+ NotoSansTeluguUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+ NotoSansTeluguUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansTeluguUI-Regular">
+ NotoSansTeluguUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Knda" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansKannada-Regular">
+ NotoSansKannada-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansKannada-Regular">
+ NotoSansKannada-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansKannada-Regular">
+ NotoSansKannada-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansKannada-Regular">
+ NotoSansKannada-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifKannada-Regular">NotoSerifKannada-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Knda" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+ NotoSansKannadaUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+ NotoSansKannadaUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+ NotoSansKannadaUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansKannadaUI-Regular">
+ NotoSansKannadaUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Orya" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansOriya">NotoSansOriya-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansOriya-Bold.ttf</font>
+ </family>
+ <family lang="und-Orya" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansOriyaUI">
+ NotoSansOriyaUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansOriyaUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Sinh" variant="elegant">
+ <font weight="400" style="normal" postScriptName="NotoSansSinhala-Regular">
+ NotoSansSinhala-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansSinhala-Regular">
+ NotoSansSinhala-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansSinhala-Regular">
+ NotoSansSinhala-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansSinhala-Regular">
+ NotoSansSinhala-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif"
+ postScriptName="NotoSerifSinhala-Regular">NotoSerifSinhala-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Sinh" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+ NotoSansSinhalaUI-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+ NotoSansSinhalaUI-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+ NotoSansSinhalaUI-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansSinhalaUI-Regular">
+ NotoSansSinhalaUI-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Khmr" variant="elegant">
+ <font weight="100" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="26.0"/>
+ </font>
+ <font weight="200" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="39.0"/>
+ </font>
+ <font weight="300" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="58.0"/>
+ </font>
+ <font weight="400" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="90.0"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="108.0"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="128.0"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="151.0"/>
+ </font>
+ <font weight="800" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="169.0"/>
+ </font>
+ <font weight="900" style="normal" postScriptName="NotoSansKhmer-Regular">
+ NotoSansKhmer-VF.ttf
+ <axis tag="wdth" stylevalue="100.0"/>
+ <axis tag="wght" stylevalue="190.0"/>
+ </font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifKhmer-Regular.otf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifKhmer-Bold.otf</font>
+ </family>
+ <family lang="und-Khmr" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansKhmerUI">
+ NotoSansKhmerUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansKhmerUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Laoo" variant="elegant">
+ <font weight="400" style="normal">NotoSansLao-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansLao-Bold.ttf</font>
+ <font weight="400" style="normal" fallbackFor="serif">
+ NotoSerifLao-Regular.ttf
+ </font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
+ </family>
+ <family lang="und-Laoo" variant="compact">
+ <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
+ </family>
+ <family lang="und-Mymr" variant="elegant">
+ <font weight="400" style="normal">NotoSansMyanmar-Regular.otf</font>
+ <font weight="500" style="normal">NotoSansMyanmar-Medium.otf</font>
+ <font weight="700" style="normal">NotoSansMyanmar-Bold.otf</font>
+ <font weight="400" style="normal" fallbackFor="serif">NotoSerifMyanmar-Regular.otf</font>
+ <font weight="700" style="normal" fallbackFor="serif">NotoSerifMyanmar-Bold.otf</font>
+ </family>
+ <family lang="und-Mymr" variant="compact">
+ <font weight="400" style="normal">NotoSansMyanmarUI-Regular.otf</font>
+ <font weight="500" style="normal">NotoSansMyanmarUI-Medium.otf</font>
+ <font weight="700" style="normal">NotoSansMyanmarUI-Bold.otf</font>
+ </family>
+ <family lang="und-Thaa">
+ <font weight="400" style="normal" postScriptName="NotoSansThaana">
+ NotoSansThaana-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansThaana-Bold.ttf</font>
+ </family>
+ <family lang="und-Cham">
+ <font weight="400" style="normal" postScriptName="NotoSansCham">NotoSansCham-Regular.ttf
+ </font>
+ <font weight="700" style="normal">NotoSansCham-Bold.ttf</font>
+ </family>
+ <family lang="und-Ahom">
+ <font weight="400" style="normal">NotoSansAhom-Regular.otf</font>
+ </family>
+ <family lang="und-Adlm">
+ <font weight="400" style="normal" postScriptName="NotoSansAdlam-Regular">
+ NotoSansAdlam-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansAdlam-Regular">
+ NotoSansAdlam-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansAdlam-Regular">
+ NotoSansAdlam-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansAdlam-Regular">
+ NotoSansAdlam-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Avst">
+ <font weight="400" style="normal" postScriptName="NotoSansAvestan">
+ NotoSansAvestan-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bali">
+ <font weight="400" style="normal" postScriptName="NotoSansBalinese">
+ NotoSansBalinese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bamu">
+ <font weight="400" style="normal" postScriptName="NotoSansBamum">NotoSansBamum-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Batk">
+ <font weight="400" style="normal" postScriptName="NotoSansBatak">NotoSansBatak-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Brah">
+ <font weight="400" style="normal" postScriptName="NotoSansBrahmi">
+ NotoSansBrahmi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Bugi">
+ <font weight="400" style="normal" postScriptName="NotoSansBuginese">
+ NotoSansBuginese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Buhd">
+ <font weight="400" style="normal" postScriptName="NotoSansBuhid">NotoSansBuhid-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cans">
+ <font weight="400" style="normal">
+ NotoSansCanadianAboriginal-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cari">
+ <font weight="400" style="normal" postScriptName="NotoSansCarian">
+ NotoSansCarian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cakm">
+ <font weight="400" style="normal">NotoSansChakma-Regular.otf</font>
+ </family>
+ <family lang="und-Cher">
+ <font weight="400" style="normal">NotoSansCherokee-Regular.ttf</font>
+ </family>
+ <family lang="und-Copt">
+ <font weight="400" style="normal" postScriptName="NotoSansCoptic">
+ NotoSansCoptic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Xsux">
+ <font weight="400" style="normal" postScriptName="NotoSansCuneiform">
+ NotoSansCuneiform-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Cprt">
+ <font weight="400" style="normal" postScriptName="NotoSansCypriot">
+ NotoSansCypriot-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Dsrt">
+ <font weight="400" style="normal" postScriptName="NotoSansDeseret">
+ NotoSansDeseret-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Egyp">
+ <font weight="400" style="normal" postScriptName="NotoSansEgyptianHieroglyphs">
+ NotoSansEgyptianHieroglyphs-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Elba">
+ <font weight="400" style="normal">NotoSansElbasan-Regular.otf</font>
+ </family>
+ <family lang="und-Glag">
+ <font weight="400" style="normal" postScriptName="NotoSansGlagolitic">
+ NotoSansGlagolitic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Goth">
+ <font weight="400" style="normal" postScriptName="NotoSansGothic">
+ NotoSansGothic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Hano">
+ <font weight="400" style="normal" postScriptName="NotoSansHanunoo">
+ NotoSansHanunoo-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Armi">
+ <font weight="400" style="normal" postScriptName="NotoSansImperialAramaic">
+ NotoSansImperialAramaic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phli">
+ <font weight="400" style="normal" postScriptName="NotoSansInscriptionalPahlavi">
+ NotoSansInscriptionalPahlavi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Prti">
+ <font weight="400" style="normal" postScriptName="NotoSansInscriptionalParthian">
+ NotoSansInscriptionalParthian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Java">
+ <font weight="400" style="normal">NotoSansJavanese-Regular.otf</font>
+ </family>
+ <family lang="und-Kthi">
+ <font weight="400" style="normal" postScriptName="NotoSansKaithi">
+ NotoSansKaithi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Kali">
+ <font weight="400" style="normal" postScriptName="NotoSansKayahLi">
+ NotoSansKayahLi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Khar">
+ <font weight="400" style="normal" postScriptName="NotoSansKharoshthi">
+ NotoSansKharoshthi-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lepc">
+ <font weight="400" style="normal" postScriptName="NotoSansLepcha">
+ NotoSansLepcha-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Limb">
+ <font weight="400" style="normal" postScriptName="NotoSansLimbu">NotoSansLimbu-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Linb">
+ <font weight="400" style="normal" postScriptName="NotoSansLinearB">
+ NotoSansLinearB-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lisu">
+ <font weight="400" style="normal" postScriptName="NotoSansLisu">NotoSansLisu-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lyci">
+ <font weight="400" style="normal" postScriptName="NotoSansLycian">
+ NotoSansLycian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lydi">
+ <font weight="400" style="normal" postScriptName="NotoSansLydian">
+ NotoSansLydian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Mand">
+ <font weight="400" style="normal" postScriptName="NotoSansMandaic">
+ NotoSansMandaic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Mtei">
+ <font weight="400" style="normal" postScriptName="NotoSansMeeteiMayek">
+ NotoSansMeeteiMayek-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Talu">
+ <font weight="400" style="normal" postScriptName="NotoSansNewTaiLue">
+ NotoSansNewTaiLue-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Nkoo">
+ <font weight="400" style="normal" postScriptName="NotoSansNKo">NotoSansNKo-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Ogam">
+ <font weight="400" style="normal" postScriptName="NotoSansOgham">NotoSansOgham-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Olck">
+ <font weight="400" style="normal" postScriptName="NotoSansOlChiki">
+ NotoSansOlChiki-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Ital">
+ <font weight="400" style="normal" postScriptName="NotoSansOldItalic">
+ NotoSansOldItalic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Xpeo">
+ <font weight="400" style="normal" postScriptName="NotoSansOldPersian">
+ NotoSansOldPersian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sarb">
+ <font weight="400" style="normal" postScriptName="NotoSansOldSouthArabian">
+ NotoSansOldSouthArabian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Orkh">
+ <font weight="400" style="normal" postScriptName="NotoSansOldTurkic">
+ NotoSansOldTurkic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Osge">
+ <font weight="400" style="normal">NotoSansOsage-Regular.ttf</font>
+ </family>
+ <family lang="und-Osma">
+ <font weight="400" style="normal" postScriptName="NotoSansOsmanya">
+ NotoSansOsmanya-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phnx">
+ <font weight="400" style="normal" postScriptName="NotoSansPhoenician">
+ NotoSansPhoenician-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Rjng">
+ <font weight="400" style="normal" postScriptName="NotoSansRejang">
+ NotoSansRejang-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Runr">
+ <font weight="400" style="normal" postScriptName="NotoSansRunic">NotoSansRunic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Samr">
+ <font weight="400" style="normal" postScriptName="NotoSansSamaritan">
+ NotoSansSamaritan-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Saur">
+ <font weight="400" style="normal" postScriptName="NotoSansSaurashtra">
+ NotoSansSaurashtra-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Shaw">
+ <font weight="400" style="normal" postScriptName="NotoSansShavian">
+ NotoSansShavian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sund">
+ <font weight="400" style="normal" postScriptName="NotoSansSundanese">
+ NotoSansSundanese-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Sylo">
+ <font weight="400" style="normal" postScriptName="NotoSansSylotiNagri">
+ NotoSansSylotiNagri-Regular.ttf
+ </font>
+ </family>
+ <!-- Esrangela should precede Eastern and Western Syriac, since it's our default form. -->
+ <family lang="und-Syre">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacEstrangela">
+ NotoSansSyriacEstrangela-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Syrn">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacEastern">
+ NotoSansSyriacEastern-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Syrj">
+ <font weight="400" style="normal" postScriptName="NotoSansSyriacWestern">
+ NotoSansSyriacWestern-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tglg">
+ <font weight="400" style="normal" postScriptName="NotoSansTagalog">
+ NotoSansTagalog-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tagb">
+ <font weight="400" style="normal" postScriptName="NotoSansTagbanwa">
+ NotoSansTagbanwa-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Lana">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiTham">
+ NotoSansTaiTham-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tavt">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiViet">
+ NotoSansTaiViet-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Tibt">
+ <font weight="400" style="normal" postScriptName="NotoSerifTibetan-Regular">
+ NotoSerifTibetan-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSerifTibetan-Regular">
+ NotoSerifTibetan-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSerifTibetan-Regular">
+ NotoSerifTibetan-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSerifTibetan-Regular">
+ NotoSerifTibetan-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Tfng">
+ <font weight="400" style="normal">NotoSansTifinagh-Regular.otf</font>
+ </family>
+ <family lang="und-Ugar">
+ <font weight="400" style="normal" postScriptName="NotoSansUgaritic">
+ NotoSansUgaritic-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Vaii">
+ <font weight="400" style="normal" postScriptName="NotoSansVai">NotoSansVai-Regular.ttf
+ </font>
+ </family>
+ <family>
+ <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted.ttf</font>
+ </family>
+ <family lang="zh-Hans">
+ <font weight="100" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="2" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="2" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="zh-Hant,zh-Bopo">
+ <font weight="100" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="3" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="3" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="ja">
+ <font weight="100" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="0" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="0" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="ko">
+ <font weight="100" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="100"/>
+ </font>
+ <font weight="200" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="200"/>
+ </font>
+ <font weight="300" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="300"/>
+ </font>
+ <font weight="400" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ <font weight="800" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="800"/>
+ </font>
+ <font weight="900" style="normal" index="1" postScriptName="NotoSansCJKjp-Thin">
+ NotoSansCJK-Regular.ttc
+ <axis tag="wght" stylevalue="900"/>
+ </font>
+ <font weight="400" style="normal" index="1" fallbackFor="serif"
+ postScriptName="NotoSerifCJKjp-Regular">NotoSerifCJK-Regular.ttc
+ </font>
+ </family>
+ <family lang="und-Zsye" ignore="true">
+ <font weight="400" style="normal">NotoColorEmojiLegacy.ttf</font>
+ </family>
+ <family lang="und-Zsye">
+ <font weight="400" style="normal">NotoColorEmoji.ttf</font>
+ </family>
+ <family lang="und-Zsye">
+ <font weight="400" style="normal">NotoColorEmojiFlags.ttf</font>
+ </family>
+ <family lang="und-Zsym">
+ <font weight="400" style="normal">NotoSansSymbols-Regular-Subsetted2.ttf</font>
+ </family>
+ <!--
+ Tai Le, Yi, Mongolian, and Phags-pa are intentionally kept last, to make sure they don't
+ override the East Asian punctuation for Chinese.
+ -->
+ <family lang="und-Tale">
+ <font weight="400" style="normal" postScriptName="NotoSansTaiLe">NotoSansTaiLe-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Yiii">
+ <font weight="400" style="normal" postScriptName="NotoSansYi">NotoSansYi-Regular.ttf</font>
+ </family>
+ <family lang="und-Mong">
+ <font weight="400" style="normal" postScriptName="NotoSansMongolian">
+ NotoSansMongolian-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Phag">
+ <font weight="400" style="normal" postScriptName="NotoSansPhagsPa">
+ NotoSansPhagsPa-Regular.ttf
+ </font>
+ </family>
+ <family lang="und-Hluw">
+ <font weight="400" style="normal">NotoSansAnatolianHieroglyphs-Regular.otf</font>
+ </family>
+ <family lang="und-Bass">
+ <font weight="400" style="normal">NotoSansBassaVah-Regular.otf</font>
+ </family>
+ <family lang="und-Bhks">
+ <font weight="400" style="normal">NotoSansBhaiksuki-Regular.otf</font>
+ </family>
+ <family lang="und-Hatr">
+ <font weight="400" style="normal">NotoSansHatran-Regular.otf</font>
+ </family>
+ <family lang="und-Lina">
+ <font weight="400" style="normal">NotoSansLinearA-Regular.otf</font>
+ </family>
+ <family lang="und-Mani">
+ <font weight="400" style="normal">NotoSansManichaean-Regular.otf</font>
+ </family>
+ <family lang="und-Marc">
+ <font weight="400" style="normal">NotoSansMarchen-Regular.otf</font>
+ </family>
+ <family lang="und-Merc">
+ <font weight="400" style="normal">NotoSansMeroitic-Regular.otf</font>
+ </family>
+ <family lang="und-Plrd">
+ <font weight="400" style="normal">NotoSansMiao-Regular.otf</font>
+ </family>
+ <family lang="und-Mroo">
+ <font weight="400" style="normal">NotoSansMro-Regular.otf</font>
+ </family>
+ <family lang="und-Mult">
+ <font weight="400" style="normal">NotoSansMultani-Regular.otf</font>
+ </family>
+ <family lang="und-Nbat">
+ <font weight="400" style="normal">NotoSansNabataean-Regular.otf</font>
+ </family>
+ <family lang="und-Newa">
+ <font weight="400" style="normal">NotoSansNewa-Regular.otf</font>
+ </family>
+ <family lang="und-Narb">
+ <font weight="400" style="normal">NotoSansOldNorthArabian-Regular.otf</font>
+ </family>
+ <family lang="und-Perm">
+ <font weight="400" style="normal">NotoSansOldPermic-Regular.otf</font>
+ </family>
+ <family lang="und-Hmng">
+ <font weight="400" style="normal">NotoSansPahawhHmong-Regular.otf</font>
+ </family>
+ <family lang="und-Palm">
+ <font weight="400" style="normal">NotoSansPalmyrene-Regular.otf</font>
+ </family>
+ <family lang="und-Pauc">
+ <font weight="400" style="normal">NotoSansPauCinHau-Regular.otf</font>
+ </family>
+ <family lang="und-Shrd">
+ <font weight="400" style="normal">NotoSansSharada-Regular.otf</font>
+ </family>
+ <family lang="und-Sora">
+ <font weight="400" style="normal">NotoSansSoraSompeng-Regular.otf</font>
+ </family>
+ <family lang="und-Gong">
+ <font weight="400" style="normal">NotoSansGunjalaGondi-Regular.otf</font>
+ </family>
+ <family lang="und-Rohg">
+ <font weight="400" style="normal">NotoSansHanifiRohingya-Regular.otf</font>
+ </family>
+ <family lang="und-Khoj">
+ <font weight="400" style="normal">NotoSansKhojki-Regular.otf</font>
+ </family>
+ <family lang="und-Gonm">
+ <font weight="400" style="normal">NotoSansMasaramGondi-Regular.otf</font>
+ </family>
+ <family lang="und-Wcho">
+ <font weight="400" style="normal">NotoSansWancho-Regular.otf</font>
+ </family>
+ <family lang="und-Wara">
+ <font weight="400" style="normal">NotoSansWarangCiti-Regular.otf</font>
+ </family>
+ <family lang="und-Gran">
+ <font weight="400" style="normal">NotoSansGrantha-Regular.ttf</font>
+ </family>
+ <family lang="und-Modi">
+ <font weight="400" style="normal">NotoSansModi-Regular.ttf</font>
+ </family>
+ <family lang="und-Dogr">
+ <font weight="400" style="normal">NotoSerifDogra-Regular.ttf</font>
+ </family>
+ <family lang="und-Medf">
+ <font weight="400" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+ NotoSansMedefaidrin-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+ NotoSansMedefaidrin-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+ NotoSansMedefaidrin-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansMedefaidrin-Regular">
+ NotoSansMedefaidrin-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Soyo">
+ <font weight="400" style="normal" postScriptName="NotoSansSoyombo-Regular">
+ NotoSansSoyombo-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansSoyombo-Regular">
+ NotoSansSoyombo-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansSoyombo-Regular">
+ NotoSansSoyombo-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansSoyombo-Regular">
+ NotoSansSoyombo-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Takr">
+ <font weight="400" style="normal" postScriptName="NotoSansTakri-Regular">
+ NotoSansTakri-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSansTakri-Regular">
+ NotoSansTakri-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSansTakri-Regular">
+ NotoSansTakri-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSansTakri-Regular">
+ NotoSansTakri-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Hmnp">
+ <font weight="400" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+ NotoSerifNyiakengPuachueHmong-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+ NotoSerifNyiakengPuachueHmong-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+ NotoSerifNyiakengPuachueHmong-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSerifHmongNyiakeng-Regular">
+ NotoSerifNyiakengPuachueHmong-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+ <family lang="und-Yezi">
+ <font weight="400" style="normal" postScriptName="NotoSerifYezidi-Regular">
+ NotoSerifYezidi-VF.ttf
+ <axis tag="wght" stylevalue="400"/>
+ </font>
+ <font weight="500" style="normal" postScriptName="NotoSerifYezidi-Regular">
+ NotoSerifYezidi-VF.ttf
+ <axis tag="wght" stylevalue="500"/>
+ </font>
+ <font weight="600" style="normal" postScriptName="NotoSerifYezidi-Regular">
+ NotoSerifYezidi-VF.ttf
+ <axis tag="wght" stylevalue="600"/>
+ </font>
+ <font weight="700" style="normal" postScriptName="NotoSerifYezidi-Regular">
+ NotoSerifYezidi-VF.ttf
+ <axis tag="wght" stylevalue="700"/>
+ </font>
+ </family>
+</familyset>
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index f6ba103f6f05..ae61a2d811d2 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -17,6 +17,7 @@
package android.graphics;
import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
+import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
import android.annotation.ColorInt;
import android.annotation.ColorLong;
@@ -133,7 +134,9 @@ public class Paint {
FAKE_BOLD_TEXT_FLAG,
LINEAR_TEXT_FLAG,
SUBPIXEL_TEXT_FLAG,
- EMBEDDED_BITMAP_TEXT_FLAG
+ EMBEDDED_BITMAP_TEXT_FLAG,
+ TEXT_RUN_FLAG_LEFT_EDGE,
+ TEXT_RUN_FLAG_RIGHT_EDGE
})
public @interface PaintFlag{}
@@ -264,6 +267,66 @@ public class Paint {
/** @hide bit mask for the flag enabling vertical rendering for text */
public static final int VERTICAL_TEXT_FLAG = 0x1000;
+ /**
+ * A text run flag that indicates the run is located the visually most left segment of the line.
+ * <p>
+ * This flag is used for telling the underlying text layout engine that the text is located at
+ * the most left of the line. This flag is used for controlling the amount letter spacing
+ * added. If the text is in the middle of the line, the text layout engine assigns additional
+ * letter spacing to the both side of each letter. On the other hand, the letter spacing should
+ * not be added to the visually most left and right of the line. By setting this flag, text
+ * layout engine calculates the layout as it is located at the most visually left of the line
+ * and doesn't add letter spacing to the left of this run.
+ * <p>
+ * Note that the caller must resolve BiDi runs and reorder them visually and set this flag only
+ * if the target run is located visually most left position. This left does not always mean the
+ * beginning of the text.
+ * <p>
+ * If the run covers entire line, caller should set {@link #TEXT_RUN_FLAG_RIGHT_EDGE} as well.
+ * <p>
+ * Note that this flag is only effective for run based APIs. For example, this flag works for
+ * {@link Canvas#drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)}
+ * and
+ * {@link Paint#getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int)}.
+ * However, this doesn't work for
+ * {@link Canvas#drawText(CharSequence, int, int, float, float, Paint)} or
+ * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
+ * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
+ */
+ @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ public static final int TEXT_RUN_FLAG_LEFT_EDGE = 0x2000;
+
+
+ /**
+ * A text run flag that indicates the run is located the visually most right segment of the
+ * line.
+ * <p>
+ * This flag is used for telling the underlying text layout engine that the text is located at
+ * the most right of the line. This flag is used for controlling the amount letter spacing
+ * added. If the text is in the middle of the line, the text layout engine assigns additional
+ * letter spacing to the both side of each letter. On the other hand, the letter spacing should
+ * not be added to the visually most left and right of the line. By setting this flag, text
+ * layout engine calculates the layout as it is located at the most visually left of the line
+ * and doesn't add letter spacing to the left of this run.
+ * <p>
+ * Note that the caller must resolve BiDi runs and reorder them visually and set this flag only
+ * if the target run is located visually most right position. This right does not always mean
+ * the end of the text.
+ * <p>
+ * If the run covers entire line, caller should set {@link #TEXT_RUN_FLAG_LEFT_EDGE} as well.
+ * <p>
+ * Note that this flag is only effective for run based APIs. For example, this flag works for
+ * {@link Canvas#drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)}
+ * and
+ * {@link Paint#getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int)}.
+ * However, this doesn't work for
+ * {@link Canvas#drawText(CharSequence, int, int, float, float, Paint)} or
+ * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
+ * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
+ */
+ @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+ public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 0x4000;
+
// These flags are always set on a new/reset paint, even if flags 0 is passed.
static final int HIDDEN_DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG | EMBEDDED_BITMAP_TEXT_FLAG
| FILTER_BITMAP_FLAG;
@@ -2520,17 +2583,24 @@ public class Paint {
if (text.length == 0 || count == 0) {
return 0f;
}
- if (!mHasCompatScaling) {
- return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
- index, count, index, count, mBidiFlags, null, 0));
- }
+ int oldFlag = getFlags();
+ setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+ try {
- final float oldSize = getTextSize();
- setTextSize(oldSize * mCompatScaling);
- final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count,
- mBidiFlags, null, 0);
- setTextSize(oldSize);
- return (float) Math.ceil(w*mInvCompatScaling);
+ if (!mHasCompatScaling) {
+ return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
+ index, count, index, count, mBidiFlags, null, 0));
+ }
+
+ final float oldSize = getTextSize();
+ setTextSize(oldSize * mCompatScaling);
+ final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count,
+ mBidiFlags, null, 0);
+ setTextSize(oldSize);
+ return (float) Math.ceil(w * mInvCompatScaling);
+ } finally {
+ setFlags(oldFlag);
+ }
}
/**
@@ -2552,16 +2622,22 @@ public class Paint {
if (text.length() == 0 || start == end) {
return 0f;
}
- if (!mHasCompatScaling) {
- return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
- start, end, start, end, mBidiFlags, null, 0));
+ int oldFlag = getFlags();
+ setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+ try {
+ if (!mHasCompatScaling) {
+ return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
+ start, end, start, end, mBidiFlags, null, 0));
+ }
+ final float oldSize = getTextSize();
+ setTextSize(oldSize * mCompatScaling);
+ final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags,
+ null, 0);
+ setTextSize(oldSize);
+ return (float) Math.ceil(w * mInvCompatScaling);
+ } finally {
+ setFlags(oldFlag);
}
- final float oldSize = getTextSize();
- setTextSize(oldSize * mCompatScaling);
- final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags,
- null, 0);
- setTextSize(oldSize);
- return (float) Math.ceil(w * mInvCompatScaling);
}
/**
@@ -2766,19 +2842,26 @@ public class Paint {
if (text.length == 0 || count == 0) {
return 0;
}
- if (!mHasCompatScaling) {
+ int oldFlag = getFlags();
+ setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+ try {
+ if (!mHasCompatScaling) {
+ nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths,
+ 0);
+ return count;
+ }
+
+ final float oldSize = getTextSize();
+ setTextSize(oldSize * mCompatScaling);
nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
+ setTextSize(oldSize);
+ for (int i = 0; i < count; i++) {
+ widths[i] *= mInvCompatScaling;
+ }
return count;
+ } finally {
+ setFlags(oldFlag);
}
-
- final float oldSize = getTextSize();
- setTextSize(oldSize * mCompatScaling);
- nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
- setTextSize(oldSize);
- for (int i = 0; i < count; i++) {
- widths[i] *= mInvCompatScaling;
- }
- return count;
}
/**
@@ -2849,19 +2932,25 @@ public class Paint {
if (text.length() == 0 || start == end) {
return 0;
}
- if (!mHasCompatScaling) {
+ int oldFlag = getFlags();
+ setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+ try {
+ if (!mHasCompatScaling) {
+ nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
+ return end - start;
+ }
+
+ final float oldSize = getTextSize();
+ setTextSize(oldSize * mCompatScaling);
nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
+ setTextSize(oldSize);
+ for (int i = 0; i < end - start; i++) {
+ widths[i] *= mInvCompatScaling;
+ }
return end - start;
+ } finally {
+ setFlags(oldFlag);
}
-
- final float oldSize = getTextSize();
- setTextSize(oldSize * mCompatScaling);
- nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
- setTextSize(oldSize);
- for (int i = 0; i < end - start; i++) {
- widths[i] *= mInvCompatScaling;
- }
- return end - start;
}
/**
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index ddae673e1084..b21bf11088e2 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -23,9 +23,7 @@ import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.compat.CompatChanges;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
+import android.app.ActivityThread;
import android.os.Build;
import android.os.LocaleList;
import android.os.Parcel;
@@ -43,15 +41,6 @@ import java.util.Objects;
* line-break property</a> for more information.
*/
public final class LineBreakConfig implements Parcelable {
-
- /**
- * A feature ID for automatic line break word style.
- * @hide
- */
- @ChangeId
- @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
- public static final long WORD_STYLE_AUTO = 280005585L;
-
/**
* No hyphenation preference is specified.
*
@@ -487,8 +476,15 @@ public final class LineBreakConfig implements Parcelable {
* @hide
*/
public static @LineBreakStyle int getResolvedLineBreakStyle(@Nullable LineBreakConfig config) {
- final int defaultStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO)
- ? LINE_BREAK_STYLE_AUTO : LINE_BREAK_STYLE_NONE;
+ final int targetSdkVersion = ActivityThread.currentApplication().getApplicationInfo()
+ .targetSdkVersion;
+ final int defaultStyle;
+ final int vicVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM;
+ if (targetSdkVersion >= vicVersion) {
+ defaultStyle = LINE_BREAK_STYLE_AUTO;
+ } else {
+ defaultStyle = LINE_BREAK_STYLE_NONE;
+ }
if (config == null) {
return defaultStyle;
}
@@ -515,8 +511,15 @@ public final class LineBreakConfig implements Parcelable {
*/
public static @LineBreakWordStyle int getResolvedLineBreakWordStyle(
@Nullable LineBreakConfig config) {
- final int defaultWordStyle = CompatChanges.isChangeEnabled(WORD_STYLE_AUTO)
- ? LINE_BREAK_WORD_STYLE_AUTO : LINE_BREAK_WORD_STYLE_NONE;
+ final int targetSdkVersion = ActivityThread.currentApplication().getApplicationInfo()
+ .targetSdkVersion;
+ final int defaultWordStyle;
+ final int vicVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM;
+ if (targetSdkVersion >= vicVersion) {
+ defaultWordStyle = LINE_BREAK_WORD_STYLE_AUTO;
+ } else {
+ defaultWordStyle = LINE_BREAK_WORD_STYLE_NONE;
+ }
if (config == null) {
return defaultWordStyle;
}
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 2d33e8d24ece..6da07198c3ad 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -269,6 +269,10 @@ public class MeasuredText {
* offset is zero. After the style is applied the internal offset is moved to {@code offset
* + length}, and next call will start from this new position.
*
+ * <p>
+ * {@link Paint#TEXT_RUN_FLAG_RIGHT_EDGE} and {@link Paint#TEXT_RUN_FLAG_LEFT_EDGE} are
+ * ignored and treated as both of them are set.
+ *
* @param paint a paint
* @param length a length to be applied with a given paint, can not exceed the length of the
* text
diff --git a/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java
index 4ec5e1b67c5d..6404c4bc33d6 100644
--- a/keystore/java/android/security/Authorization.java
+++ b/keystore/java/android/security/Authorization.java
@@ -100,12 +100,14 @@ public class Authorization {
*
* @param userId - the user's Android user ID
* @param unlockingSids - list of biometric SIDs with which the device may be unlocked again
+ * @param weakUnlockEnabled - true if non-strong biometric or trust agent unlock is enabled
* @return 0 if successful or a {@code ResponseCode}.
*/
- public static int onDeviceLocked(int userId, @NonNull long[] unlockingSids) {
+ public static int onDeviceLocked(int userId, @NonNull long[] unlockingSids,
+ boolean weakUnlockEnabled) {
StrictMode.noteDiskWrite();
try {
- getService().onDeviceLocked(userId, unlockingSids);
+ getService().onDeviceLocked(userId, unlockingSids, weakUnlockEnabled);
return 0;
} catch (RemoteException | NullPointerException e) {
Log.w(TAG, "Can not connect to keystore", e);
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 45540e0fbbb8..4cdc06a999a7 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -162,6 +162,7 @@ android_library {
"com_android_wm_shell_flags_lib",
"com.android.window.flags.window-aconfig-java",
"WindowManager-Shell-proto",
+ "perfetto_trace_java_protos",
"dagger2",
"jsr330",
],
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 901d5fa0cd9a..0e046581cb48 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -1,13 +1,6 @@
package: "com.android.wm.shell"
flag {
- name: "example_flag"
- namespace: "multitasking"
- description: "An Example Flag"
- bug: "300136750"
-}
-
-flag {
name: "enable_app_pairs"
namespace: "multitasking"
description: "Enables the ability to create and save app pairs to the Home screen"
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index e4abae48c8fd..9854e58dd7cf 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -134,6 +134,13 @@
<!-- Whether the additional education about reachability is enabled -->
<bool name="config_letterboxIsReachabilityEducationEnabled">false</bool>
+ <!-- The minimum tolerance of the percentage of activity bounds within its task to hide
+ size compat restart button. Value lower than 0 or higher than 100 will be ignored.
+ 100 is the default value where the activity has to fit exactly within the task to allow
+ size compat restart button to be hidden. 0 means size compat restart button will always
+ be hidden. -->
+ <integer name="config_letterboxRestartButtonHideTolerance">100</integer>
+
<!-- Whether DragAndDrop capability is enabled -->
<bool name="config_enableShellDragDrop">true</bool>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index bb433dbbd2ce..e7f6f0d61847 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -17,7 +17,7 @@
package com.android.wm.shell.back;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_PREDICTIVE_BACK_HOME;
-import static com.android.window.flags.Flags.predictiveBackSystemAnimations;
+import static com.android.window.flags.Flags.predictiveBackSystemAnims;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
@@ -244,7 +244,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private void setupAnimationDeveloperSettingsObserver(
@NonNull ContentResolver contentResolver,
@NonNull @ShellBackgroundThread final Handler backgroundHandler) {
- if (predictiveBackSystemAnimations()) {
+ if (predictiveBackSystemAnims()) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation aconfig flag is enabled, therefore "
+ "developer settings flag is ignored and no content observer registered");
return;
@@ -267,7 +267,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
*/
@ShellBackgroundThread
private void updateEnableAnimationFromFlags() {
- boolean isEnabled = predictiveBackSystemAnimations() || isDeveloperSettingEnabled();
+ boolean isEnabled = predictiveBackSystemAnims() || isDeveloperSettingEnabled();
mEnableAnimations.set(isEnabled);
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s", isEnabled);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index e48732801094..bb0dd95b042f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -769,8 +769,10 @@ public class StackAnimationController extends
boolean swapped = false;
for (int newIndex = 0; newIndex < bubbleViews.size(); newIndex++) {
View view = bubbleViews.get(newIndex);
- final int oldIndex = mLayout.indexOfChild(view);
- swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after);
+ if (view != null) {
+ final int oldIndex = mLayout.indexOfChild(view);
+ swapped |= animateSwap(view, oldIndex, newIndex, updateAllIcons, after);
+ }
}
if (!swapped) {
// All bubbles were at the right position. Make sure badges and z order is correct.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 12114519d086..bd8ce803c591 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -26,6 +26,7 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.ColorDrawable;
+import android.view.Gravity;
import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewTreeObserver;
@@ -74,10 +75,6 @@ public class BubbleBarLayerView extends FrameLayout
private DismissView mDismissView;
private @Nullable Consumer<String> mUnBubbleConversationCallback;
- // TODO(b/273310265) - currently the view is always on the right, need to update for RTL.
- /** Whether the expanded view is displaying on the left of the screen or not. */
- private boolean mOnLeft = false;
-
/** Whether a bubble is expanded. */
private boolean mIsExpanded = false;
@@ -154,10 +151,10 @@ public class BubbleBarLayerView extends FrameLayout
return mIsExpanded;
}
- // (TODO: b/273310265): BubblePositioner should be source of truth when this work is done.
+ // TODO(b/313661121) - when dragging is implemented, check user setting first
/** Whether the expanded view is positioned on the left or right side of the screen. */
public boolean isOnLeft() {
- return mOnLeft;
+ return getLayoutDirection() == LAYOUT_DIRECTION_RTL;
}
/** Shows the expanded view of the provided bubble. */
@@ -216,7 +213,7 @@ public class BubbleBarLayerView extends FrameLayout
return Unit.INSTANCE;
});
- addView(mExpandedView, new FrameLayout.LayoutParams(width, height));
+ addView(mExpandedView, new LayoutParams(width, height, Gravity.LEFT));
}
if (mEducationViewController.isEducationVisible()) {
@@ -311,7 +308,7 @@ public class BubbleBarLayerView extends FrameLayout
lp.width = width;
lp.height = height;
mExpandedView.setLayoutParams(lp);
- if (mOnLeft) {
+ if (isOnLeft()) {
mExpandedView.setX(mPositioner.getInsets().left + padding);
} else {
mExpandedView.setX(mPositioner.getAvailableRect().width() - width - padding);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
index 09d99b204bdb..fa2e23647a39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -71,6 +71,8 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
private static final String HAS_SEEN_VERTICAL_REACHABILITY_EDUCATION_KEY_PREFIX =
"has_seen_vertical_reachability_education";
+ private static final int MAX_PERCENTAGE_VAL = 100;
+
/**
* The {@link SharedPreferences} instance for the restart dialog and the reachability
* education.
@@ -82,6 +84,12 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
*/
private final SharedPreferences mLetterboxEduSharedPreferences;
+ /**
+ * The minimum tolerance of the percentage of activity bounds within its task to hide
+ * size compat restart button.
+ */
+ private final int mHideSizeCompatRestartButtonTolerance;
+
// Whether the extended restart dialog is enabled
private boolean mIsRestartDialogEnabled;
@@ -106,6 +114,9 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
R.bool.config_letterboxIsRestartDialogEnabled);
mIsReachabilityEducationEnabled = context.getResources().getBoolean(
R.bool.config_letterboxIsReachabilityEducationEnabled);
+ final int tolerance = context.getResources().getInteger(
+ R.integer.config_letterboxRestartButtonHideTolerance);
+ mHideSizeCompatRestartButtonTolerance = getHideSizeCompatRestartButtonTolerance(tolerance);
mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG,
DEFAULT_VALUE_ENABLE_LETTERBOX_RESTART_DIALOG);
@@ -179,6 +190,10 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
|| !hasSeenVerticalReachabilityEducation(taskInfo));
}
+ int getHideSizeCompatRestartButtonTolerance() {
+ return mHideSizeCompatRestartButtonTolerance;
+ }
+
boolean getHasSeenLetterboxEducation(int userId) {
return mLetterboxEduSharedPreferences
.getBoolean(dontShowLetterboxEduKey(userId), /* default= */ false);
@@ -218,6 +233,15 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
}
}
+ // Returns the minimum tolerance of the percentage of activity bounds within its task to hide
+ // size compat restart button. Value lower than 0 or higher than 100 will be ignored.
+ // 100 is the default value where the activity has to fit exactly within the task to allow
+ // size compat restart button to be hidden. 0 means size compat restart button will always
+ // be hidden.
+ private int getHideSizeCompatRestartButtonTolerance(int tolerance) {
+ return tolerance < 0 || tolerance > MAX_PERCENTAGE_VAL ? MAX_PERCENTAGE_VAL : tolerance;
+ }
+
private boolean isReachabilityEducationEnabled() {
return mIsReachabilityEducationOverrideEnabled || (mIsReachabilityEducationEnabled
&& mIsLetterboxReachabilityEducationAllowed);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
index 00e0cdb034b6..2dd27430e348 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIWindowManager.java
@@ -22,7 +22,9 @@ import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPL
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppCompatTaskInfo;
import android.app.AppCompatTaskInfo.CameraCompatControlState;
import android.app.TaskInfo;
import android.content.Context;
@@ -33,6 +35,7 @@ import android.view.LayoutInflater;
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayLayout;
@@ -68,6 +71,8 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
@VisibleForTesting
CompatUILayout mLayout;
+ private final float mHideScmTolerance;
+
CompatUIWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue, CompatUICallback callback,
ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
@@ -75,11 +80,13 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
mCallback = callback;
- mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+ mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat
+ && shouldShowSizeCompatRestartButton(taskInfo);
mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState;
mCompatUIHintsState = compatUIHintsState;
mCompatUIConfiguration = compatUIConfiguration;
mOnRestartButtonClicked = onRestartButtonClicked;
+ mHideScmTolerance = mCompatUIConfiguration.getHideSizeCompatRestartButtonTolerance();
}
@Override
@@ -107,6 +114,11 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
mLayout = inflateLayout();
mLayout.inject(this);
+ final TaskInfo taskInfo = getLastTaskInfo();
+ if (taskInfo != null) {
+ mHasSizeCompat = mHasSizeCompat && shouldShowSizeCompatRestartButton(taskInfo);
+ }
+
updateVisibilityOfViews();
if (mHasSizeCompat) {
@@ -127,7 +139,8 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
boolean canShow) {
final boolean prevHasSizeCompat = mHasSizeCompat;
final int prevCameraCompatControlState = mCameraCompatControlState;
- mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat;
+ mHasSizeCompat = taskInfo.appCompatTaskInfo.topActivityInSizeCompat
+ && shouldShowSizeCompatRestartButton(taskInfo);
mCameraCompatControlState = taskInfo.appCompatTaskInfo.cameraCompatControlState;
if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
@@ -208,6 +221,30 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
updateSurfacePosition(positionX, positionY);
}
+ @VisibleForTesting
+ boolean shouldShowSizeCompatRestartButton(@NonNull TaskInfo taskInfo) {
+ if (!Flags.allowHideScmButton()) {
+ return true;
+ }
+ final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
+ final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
+ final int letterboxArea = computeArea(appCompatTaskInfo.topActivityLetterboxWidth,
+ appCompatTaskInfo.topActivityLetterboxHeight);
+ final int taskArea = computeArea(taskBounds.width(), taskBounds.height());
+ if (letterboxArea == 0 || taskArea == 0) {
+ return false;
+ }
+ final float percentageAreaOfLetterboxInTask = (float) letterboxArea / taskArea * 100;
+ return percentageAreaOfLetterboxInTask < mHideScmTolerance;
+ }
+
+ private int computeArea(int width, int height) {
+ if (width == 0 || height == 0) {
+ return 0;
+ }
+ return width * height;
+ }
+
private void updateVisibilityOfViews() {
if (mLayout == null) {
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
index 95d7ad5c416f..c3a82ce258df 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt
@@ -99,7 +99,11 @@ class DragToDesktopTransitionHandler(
windowDecoration: DesktopModeWindowDecoration
) {
if (inProgress) {
- error("A drag to desktop is already in progress")
+ KtProtoLog.v(
+ ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+ "DragToDesktop: Drag to desktop transition already in progress."
+ )
+ return
}
val options = ActivityOptions.makeBasic().apply {
@@ -144,6 +148,12 @@ class DragToDesktopTransitionHandler(
* inside the desktop drop zone.
*/
fun finishDragToDesktopTransition(wct: WindowContainerTransaction) {
+ if (!inProgress) {
+ // Don't attempt to finish a drag to desktop transition since there is no transition in
+ // progress which means that the drag to desktop transition was never successfully
+ // started.
+ return
+ }
if (requireTransitionState().startAborted) {
// Don't attempt to complete the drag-to-desktop since the start transition didn't
// succeed as expected. Just reset the state as if nothing happened.
@@ -161,6 +171,12 @@ class DragToDesktopTransitionHandler(
* means the user wants to remain in their current windowing mode.
*/
fun cancelDragToDesktopTransition() {
+ if (!inProgress) {
+ // Don't attempt to cancel a drag to desktop transition since there is no transition in
+ // progress which means that the drag to desktop transition was never successfully
+ // started.
+ return
+ }
val state = requireTransitionState()
if (state.startAborted) {
// Don't attempt to cancel the drag-to-desktop since the start transition didn't
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 b0d8b47b170a..3fb0dbfaa63d 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
@@ -83,6 +83,9 @@ import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.sysui.ShellCommandHandler;
import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.tracing.LegacyTransitionTracer;
+import com.android.wm.shell.transition.tracing.PerfettoTransitionTracer;
+import com.android.wm.shell.transition.tracing.TransitionTracer;
import com.android.wm.shell.util.TransitionUtil;
import java.io.PrintWriter;
@@ -184,7 +187,7 @@ public class Transitions implements RemoteCallable<Transitions>,
private final ShellController mShellController;
private final ShellTransitionImpl mImpl = new ShellTransitionImpl();
private final SleepHandler mSleepHandler = new SleepHandler();
- private final Tracer mTracer = new Tracer();
+ private final TransitionTracer mTransitionTracer;
private boolean mIsRegistered = false;
/** List of possible handlers. Ordered by specificity (eg. tapped back to front). */
@@ -307,6 +310,12 @@ public class Transitions implements RemoteCallable<Transitions>,
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "addHandler: Remote");
shellInit.addInitCallback(this::onInit, this);
mHomeTransitionObserver = observer;
+
+ if (android.tracing.Flags.perfettoTransitionTracing()) {
+ mTransitionTracer = new PerfettoTransitionTracer();
+ } else {
+ mTransitionTracer = new LegacyTransitionTracer();
+ }
}
private void onInit() {
@@ -868,7 +877,7 @@ public class Transitions implements RemoteCallable<Transitions>,
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
+ " %s is still animating. Notify the animating transition"
+ " in case they can be merged", ready, playing);
- mTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
+ mTransitionTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
playing.mToken, (wct) -> onMerged(playing, ready));
}
@@ -902,7 +911,7 @@ public class Transitions implements RemoteCallable<Transitions>,
for (int i = 0; i < mObservers.size(); ++i) {
mObservers.get(i).onTransitionMerged(merged.mToken, playing.mToken);
}
- mTracer.logMerged(merged.mInfo.getDebugId(), playing.mInfo.getDebugId());
+ mTransitionTracer.logMerged(merged.mInfo.getDebugId(), playing.mInfo.getDebugId());
// See if we should merge another transition.
processReadyQueue(track);
}
@@ -923,7 +932,7 @@ public class Transitions implements RemoteCallable<Transitions>,
active.mStartT, active.mFinishT, (wct) -> onFinish(active, wct));
if (consumed) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
- mTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
+ mTransitionTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
return;
}
}
@@ -948,7 +957,7 @@ public class Transitions implements RemoteCallable<Transitions>,
if (consumed) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s",
mHandlers.get(i));
- mTracer.logDispatched(info.getDebugId(), mHandlers.get(i));
+ mTransitionTracer.logDispatched(info.getDebugId(), mHandlers.get(i));
return mHandlers.get(i);
}
}
@@ -978,7 +987,7 @@ public class Transitions implements RemoteCallable<Transitions>,
final Track track = mTracks.get(transition.getTrack());
transition.mAborted = true;
- mTracer.logAborted(transition.mInfo.getDebugId());
+ mTransitionTracer.logAborted(transition.mInfo.getDebugId());
if (transition.mHandler != null) {
// Notifies to clean-up the aborted transition.
@@ -1506,12 +1515,18 @@ public class Transitions implements RemoteCallable<Transitions>,
}
}
-
@Override
public boolean onShellCommand(String[] args, PrintWriter pw) {
switch (args[0]) {
case "tracing": {
- mTracer.onShellCommand(Arrays.copyOfRange(args, 1, args.length), pw);
+ if (!android.tracing.Flags.perfettoTransitionTracing()) {
+ ((LegacyTransitionTracer) mTransitionTracer)
+ .onShellCommand(Arrays.copyOfRange(args, 1, args.length), pw);
+ } else {
+ pw.println("Command not supported. Use the Perfetto command instead to start "
+ + "and stop this trace instead.");
+ return false;
+ }
return true;
}
default: {
@@ -1524,8 +1539,10 @@ public class Transitions implements RemoteCallable<Transitions>,
@Override
public void printShellCommandHelp(PrintWriter pw, String prefix) {
- pw.println(prefix + "tracing");
- mTracer.printShellCommandHelp(pw, prefix + " ");
+ if (!android.tracing.Flags.perfettoTransitionTracing()) {
+ pw.println(prefix + "tracing");
+ ((LegacyTransitionTracer) mTransitionTracer).printShellCommandHelp(pw, prefix + " ");
+ }
}
private void dump(@NonNull PrintWriter pw, String prefix) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java
index 5919aad133c7..9c848869e0f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Tracer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/LegacyTransitionTracer.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.wm.shell.transition;
+package com.android.wm.shell.transition.tracing;
import static android.os.Build.IS_USER;
@@ -29,6 +29,7 @@ import android.util.Log;
import com.android.internal.util.TraceBuffer;
import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.transition.Transitions;
import com.google.protobuf.nano.MessageNano;
@@ -45,7 +46,8 @@ import java.util.concurrent.TimeUnit;
/**
* Helper class to collect and dump transition traces.
*/
-public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
+public class LegacyTransitionTracer
+ implements ShellCommandHandler.ShellCommandActionHandler, TransitionTracer {
private static final int ALWAYS_ON_TRACING_CAPACITY = 15 * 1024; // 15 KB
private static final int ACTIVE_TRACING_BUFFER_CAPACITY = 5000 * 1024; // 5 MB
@@ -60,33 +62,33 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
private final TraceBuffer.ProtoProvider mProtoProvider =
new TraceBuffer.ProtoProvider<MessageNano,
- com.android.wm.shell.nano.WmShellTransitionTraceProto,
- com.android.wm.shell.nano.Transition>() {
- @Override
- public int getItemSize(MessageNano proto) {
- return proto.getCachedSize();
- }
-
- @Override
- public byte[] getBytes(MessageNano proto) {
- return MessageNano.toByteArray(proto);
- }
-
- @Override
- public void write(
- com.android.wm.shell.nano.WmShellTransitionTraceProto encapsulatingProto,
- Queue<com.android.wm.shell.nano.Transition> buffer, OutputStream os)
+ com.android.wm.shell.nano.WmShellTransitionTraceProto,
+ com.android.wm.shell.nano.Transition>() {
+ @Override
+ public int getItemSize(MessageNano proto) {
+ return proto.getCachedSize();
+ }
+
+ @Override
+ public byte[] getBytes(MessageNano proto) {
+ return MessageNano.toByteArray(proto);
+ }
+
+ @Override
+ public void write(
+ com.android.wm.shell.nano.WmShellTransitionTraceProto encapsulatingProto,
+ Queue<com.android.wm.shell.nano.Transition> buffer, OutputStream os)
throws IOException {
- encapsulatingProto.transitions = buffer.toArray(
- new com.android.wm.shell.nano.Transition[0]);
- os.write(getBytes(encapsulatingProto));
- }
- };
+ encapsulatingProto.transitions = buffer.toArray(
+ new com.android.wm.shell.nano.Transition[0]);
+ os.write(getBytes(encapsulatingProto));
+ }
+ };
private final TraceBuffer<MessageNano,
com.android.wm.shell.nano.WmShellTransitionTraceProto,
- com.android.wm.shell.nano.Transition> mTraceBuffer
- = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY, mProtoProvider,
- (proto) -> handleOnEntryRemovedFromTrace(proto));
+ com.android.wm.shell.nano.Transition> mTraceBuffer =
+ new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY, mProtoProvider,
+ this::handleOnEntryRemovedFromTrace);
private final Map<Object, Runnable> mRemovedFromTraceCallbacks = new HashMap<>();
private final Map<Transitions.TransitionHandler, Integer> mHandlerIds = new HashMap<>();
@@ -99,6 +101,7 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
* @param transitionId The id of the transition being dispatched.
* @param handler The handler the transition is being dispatched to.
*/
+ @Override
public void logDispatched(int transitionId, Transitions.TransitionHandler handler) {
final int handlerId;
if (mHandlerIds.containsKey(handler)) {
@@ -130,6 +133,7 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
*
* @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
*/
+ @Override
public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
proto.id = mergeRequestedTransitionId;
@@ -145,6 +149,7 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
* @param mergedTransitionId The id of the transition that was merged.
* @param playingTransitionId The id of the transition the transition was merged into.
*/
+ @Override
public void logMerged(int mergedTransitionId, int playingTransitionId) {
com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
proto.id = mergedTransitionId;
@@ -159,6 +164,7 @@ public class Tracer implements ShellCommandHandler.ShellCommandActionHandler {
*
* @param transitionId The id of the transition that was aborted.
*/
+ @Override
public void logAborted(int transitionId) {
com.android.wm.shell.nano.Transition proto = new com.android.wm.shell.nano.Transition();
proto.id = transitionId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
new file mode 100644
index 000000000000..99df6a31beac
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/PerfettoTransitionTracer.java
@@ -0,0 +1,174 @@
+/*
+ * 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.wm.shell.transition.tracing;
+
+import android.internal.perfetto.protos.PerfettoTrace;
+import android.os.SystemClock;
+import android.tracing.perfetto.DataSourceInstance;
+import android.tracing.perfetto.DataSourceParams;
+import android.tracing.perfetto.InitArguments;
+import android.tracing.perfetto.Producer;
+import android.tracing.perfetto.TracingContext;
+import android.tracing.transition.TransitionDataSource;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.wm.shell.transition.Transitions;
+
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Helper class to collect and dump transition traces.
+ */
+public class PerfettoTransitionTracer implements TransitionTracer {
+ private final AtomicInteger mActiveTraces = new AtomicInteger(0);
+ private final TransitionDataSource mDataSource = new TransitionDataSource(
+ mActiveTraces::incrementAndGet,
+ this::onFlush,
+ mActiveTraces::decrementAndGet);
+
+ public PerfettoTransitionTracer() {
+ Producer.init(InitArguments.DEFAULTS);
+ mDataSource.register(DataSourceParams.DEFAULTS);
+ }
+
+ /**
+ * Adds an entry in the trace to log that a transition has been dispatched to a handler.
+ *
+ * @param transitionId The id of the transition being dispatched.
+ * @param handler The handler the transition is being dispatched to.
+ */
+ @Override
+ public void logDispatched(int transitionId, Transitions.TransitionHandler handler) {
+ if (!isTracing()) {
+ return;
+ }
+
+ mDataSource.trace(ctx -> {
+ final int handlerId = getHandlerId(handler, ctx);
+
+ final ProtoOutputStream os = ctx.newTracePacket();
+ final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+ os.write(PerfettoTrace.ShellTransition.ID, transitionId);
+ os.write(PerfettoTrace.ShellTransition.DISPATCH_TIME_NS,
+ SystemClock.elapsedRealtimeNanos());
+ os.write(PerfettoTrace.ShellTransition.HANDLER, handlerId);
+ os.end(token);
+ });
+ }
+
+ private static int getHandlerId(Transitions.TransitionHandler handler,
+ TracingContext<DataSourceInstance, TransitionDataSource.TlsState, Void> ctx) {
+ final Map<String, Integer> handlerMapping =
+ ctx.getCustomTlsState().handlerMapping;
+ final int handlerId;
+ if (handlerMapping.containsKey(handler.getClass().getName())) {
+ handlerId = handlerMapping.get(handler.getClass().getName());
+ } else {
+ // + 1 to avoid 0 ids which can be confused with missing value when dumped to proto
+ handlerId = handlerMapping.size() + 1;
+ handlerMapping.put(handler.getClass().getName(), handlerId);
+ }
+ return handlerId;
+ }
+
+ /**
+ * Adds an entry in the trace to log that a request to merge a transition was made.
+ *
+ * @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
+ */
+ @Override
+ public void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId) {
+ if (!isTracing()) {
+ return;
+ }
+
+ mDataSource.trace(ctx -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+ final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+ os.write(PerfettoTrace.ShellTransition.ID, mergeRequestedTransitionId);
+ os.write(PerfettoTrace.ShellTransition.MERGE_REQUEST_TIME_NS,
+ SystemClock.elapsedRealtimeNanos());
+ os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId);
+ os.end(token);
+ });
+ }
+
+ /**
+ * Adds an entry in the trace to log that a transition was merged by the handler.
+ *
+ * @param mergedTransitionId The id of the transition that was merged.
+ * @param playingTransitionId The id of the transition the transition was merged into.
+ */
+ @Override
+ public void logMerged(int mergedTransitionId, int playingTransitionId) {
+ if (!isTracing()) {
+ return;
+ }
+
+ mDataSource.trace(ctx -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+ final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+ os.write(PerfettoTrace.ShellTransition.ID, mergedTransitionId);
+ os.write(PerfettoTrace.ShellTransition.MERGE_TIME_NS,
+ SystemClock.elapsedRealtimeNanos());
+ os.write(PerfettoTrace.ShellTransition.MERGE_TARGET, playingTransitionId);
+ os.end(token);
+ });
+ }
+
+ /**
+ * Adds an entry in the trace to log that a transition was aborted.
+ *
+ * @param transitionId The id of the transition that was aborted.
+ */
+ @Override
+ public void logAborted(int transitionId) {
+ if (!isTracing()) {
+ return;
+ }
+
+ mDataSource.trace(ctx -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+ final long token = os.start(PerfettoTrace.TracePacket.SHELL_TRANSITION);
+ os.write(PerfettoTrace.ShellTransition.ID, transitionId);
+ os.write(PerfettoTrace.ShellTransition.SHELL_ABORT_TIME_NS,
+ SystemClock.elapsedRealtimeNanos());
+ os.end(token);
+ });
+ }
+
+ private boolean isTracing() {
+ return mActiveTraces.get() > 0;
+ }
+
+ private void onFlush() {
+ mDataSource.trace(ctx -> {
+ final ProtoOutputStream os = ctx.newTracePacket();
+
+ final Map<String, Integer> handlerMapping = ctx.getCustomTlsState().handlerMapping;
+ for (String handler : handlerMapping.keySet()) {
+ final long token = os.start(PerfettoTrace.TracePacket.SHELL_HANDLER_MAPPINGS);
+ os.write(PerfettoTrace.ShellHandlerMapping.ID, handlerMapping.get(handler));
+ os.write(PerfettoTrace.ShellHandlerMapping.NAME, handler);
+ os.end(token);
+ }
+
+ ctx.flush();
+ });
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java
new file mode 100644
index 000000000000..5857ad88e9e6
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/tracing/TransitionTracer.java
@@ -0,0 +1,51 @@
+/*
+ * 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.wm.shell.transition.tracing;
+
+import com.android.wm.shell.transition.Transitions;
+
+public interface TransitionTracer {
+ /**
+ * Adds an entry in the trace to log that a transition has been dispatched to a handler.
+ *
+ * @param transitionId The id of the transition being dispatched.
+ * @param handler The handler the transition is being dispatched to.
+ */
+ void logDispatched(int transitionId, Transitions.TransitionHandler handler);
+
+ /**
+ * Adds an entry in the trace to log that a request to merge a transition was made.
+ *
+ * @param mergeRequestedTransitionId The id of the transition we are requesting to be merged.
+ */
+ void logMergeRequested(int mergeRequestedTransitionId, int playingTransitionId);
+
+ /**
+ * Adds an entry in the trace to log that a transition was merged by the handler.
+ *
+ * @param mergedTransitionId The id of the transition that was merged.
+ * @param playingTransitionId The id of the transition the transition was merged into.
+ */
+ void logMerged(int mergedTransitionId, int playingTransitionId);
+
+ /**
+ * Adds an entry in the trace to log that a transition was aborted.
+ *
+ * @param transitionId The id of the transition that was aborted.
+ */
+ void logAborted(int transitionId);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
index 4aed7c449750..e6d35e83116b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
@@ -16,8 +16,6 @@
package com.android.wm.shell.unfold;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-
import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
@@ -230,8 +228,7 @@ public class UnfoldAnimationController implements UnfoldListener {
}
private void maybeResetTask(UnfoldTaskAnimator animator, TaskInfo taskInfo) {
- // TODO(b/311084698): the windowing mode check is added here as a work around.
- if (!mIsInStageChange || taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
+ if (!mIsInStageChange) {
// No need to resetTask if there is no ongoing state change.
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 1a793a16f254..b2eeea7048bc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -271,6 +271,9 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
if (e.findPointerIndex(mDragPointerId) == -1) {
mDragPointerId = e.getPointerId(0);
}
+ final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
+ // If a decor's resize drag zone is active, don't also try to reposition it.
+ if (decoration.isHandlingDragResize()) break;
final int dragPointerIdx = e.findPointerIndex(mDragPointerId);
mDragPositioningCallback.onDragPositioningMove(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 1debb02e86af..5a74255df49a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -286,6 +286,10 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
closeBackground.setTintList(buttonTintColor);
}
+ boolean isHandlingDragResize() {
+ return mDragResizeListener.isHandlingDragResize();
+ }
+
private void closeDragResizeListener() {
if (mDragResizeListener == null) {
return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index aabc1cfb5875..554b1fb99550 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -491,8 +491,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return true;
}
case MotionEvent.ACTION_MOVE: {
+ mShouldClick = false;
final DesktopModeWindowDecoration decoration =
mWindowDecorByTaskId.get(mTaskId);
+ // If a decor's resize drag zone is active, don't also try to reposition it.
+ if (decoration.isHandlingDragResize()) break;
decoration.closeMaximizeMenu();
if (e.findPointerIndex(mDragPointerId) == -1) {
mDragPointerId = e.getPointerId(0);
@@ -505,7 +508,6 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
e.getRawX(dragPointerIdx),
newTaskBounds));
mIsDragging = true;
- mShouldClick = false;
return true;
}
case MotionEvent.ACTION_UP:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 53f806ccabee..20233331997f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -387,6 +387,10 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
return mHandleMenu != null;
}
+ boolean isHandlingDragResize() {
+ return mDragResizeListener.isHandlingDragResize();
+ }
+
private void loadAppInfo() {
String packageName = mTaskInfo.realActivity.getPackageName();
PackageManager pm = mContext.getApplicationContext().getPackageManager();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 8511a21d4294..8b38f991a2db 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -320,6 +320,10 @@ class DragResizeInputListener implements AutoCloseable {
}
}
+ boolean isHandlingDragResize() {
+ return mInputEventReceiver.isHandlingEvents();
+ }
+
@Override
public void close() {
mInputEventReceiver.dispose();
@@ -386,6 +390,10 @@ class DragResizeInputListener implements AutoCloseable {
finishInputEvent(inputEvent, handleInputEvent(inputEvent));
}
+ boolean isHandlingEvents() {
+ return mShouldHandleEvents;
+ }
+
private boolean handleInputEvent(InputEvent inputEvent) {
if (!(inputEvent instanceof MotionEvent)) {
return false;
@@ -409,7 +417,6 @@ class DragResizeInputListener implements AutoCloseable {
mShouldHandleEvents = isInResizeHandleBounds(x, y);
}
if (mShouldHandleEvents) {
- mInputManager.pilferPointers(mInputChannel.getToken());
mDragPointerId = e.getPointerId(0);
float rawX = e.getRawX(0);
float rawY = e.getRawY(0);
@@ -427,6 +434,7 @@ class DragResizeInputListener implements AutoCloseable {
if (!mShouldHandleEvents) {
break;
}
+ mInputManager.pilferPointers(mInputChannel.getToken());
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
float rawX = e.getRawX(dragPointerIndex);
float rawY = e.getRawY(dragPointerIndex);
@@ -437,6 +445,7 @@ class DragResizeInputListener implements AutoCloseable {
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
+ mInputManager.pilferPointers(mInputChannel.getToken());
if (mShouldHandleEvents) {
int dragPointerIndex = e.findPointerIndex(mDragPointerId);
final Rect taskBounds = mCallback.onDragPositioningEnd(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index c1b18f959641..7c6e69eb1ec9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -61,6 +61,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
private int mCtrlType;
private boolean mIsResizingOrAnimatingResize;
@Surface.Rotation private int mRotation;
+ private boolean mVeilIsVisible;
public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
DesktopModeWindowDecoration windowDecoration,
@@ -94,7 +95,6 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
mDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
mRepositionStartPoint.set(x, y);
if (isResizing()) {
- mDesktopWindowDecoration.showResizeVeil(mTaskBoundsAtDragStart);
if (!mDesktopWindowDecoration.mTaskInfo.isFocused) {
WindowContainerTransaction wct = new WindowContainerTransaction();
wct.reorder(mDesktopWindowDecoration.mTaskInfo.token, true);
@@ -119,8 +119,13 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType,
mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
mDisplayController, mDesktopWindowDecoration)) {
- mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
mIsResizingOrAnimatingResize = true;
+ if (!mVeilIsVisible) {
+ mDesktopWindowDecoration.showResizeVeil(mRepositionTaskBounds);
+ mVeilIsVisible = true;
+ } else {
+ mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
+ }
} else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
final SurfaceControl.Transaction t = mTransactionSupplier.get();
DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
@@ -143,7 +148,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
- } else {
+ } else if (mVeilIsVisible) {
// If bounds haven't changed, perform necessary veil reset here as startAnimation
// won't be called.
mDesktopWindowDecoration.hideResizeVeil();
@@ -163,6 +168,7 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback,
mCtrlType = CTRL_TYPE_UNDEFINED;
mTaskBoundsAtDragStart.setEmpty();
mRepositionStartPoint.set(0, 0);
+ mVeilIsVisible = false;
return new Rect(mRepositionTaskBounds);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index d4b97ed55192..2acfd83084ab 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -20,6 +20,8 @@ import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_DISMISSED;
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
import static android.app.AppCompatTaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.WindowInsets.Type.navigationBars;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -27,6 +29,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
@@ -39,6 +42,7 @@ import android.app.AppCompatTaskInfo;
import android.app.TaskInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.testing.AndroidTestingRunner;
import android.util.Pair;
import android.view.DisplayInfo;
@@ -50,6 +54,7 @@ import android.view.View;
import androidx.test.filters.SmallTest;
+import com.android.window.flags.Flags;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
@@ -59,6 +64,7 @@ import com.android.wm.shell.compatui.CompatUIController.CompatUIHintsState;
import junit.framework.Assert;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -76,6 +82,8 @@ import java.util.function.Consumer;
@RunWith(AndroidTestingRunner.class)
@SmallTest
public class CompatUIWindowManagerTest extends ShellTestCase {
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
private static final int TASK_ID = 1;
@@ -107,6 +115,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
public void testCreateSizeCompatButton() {
// Doesn't create layout if show is false.
mWindowManager.mHasSizeCompat = true;
+ doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo);
assertTrue(mWindowManager.createLayout(/* canShow= */ false));
verify(mWindowManager, never()).inflateLayout();
@@ -199,6 +208,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
// No diff
clearInvocations(mWindowManager);
TaskInfo taskInfo = createTaskInfo(/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN);
+ doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(any());
assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true));
verify(mWindowManager, never()).updateSurfacePosition();
@@ -284,6 +294,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
@Test
public void testUpdateCompatInfoLayoutNotInflatedYet() {
mWindowManager.mHasSizeCompat = true;
+ doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(any());
mWindowManager.createLayout(/* canShow= */ false);
verify(mWindowManager, never()).inflateLayout();
@@ -353,6 +364,7 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
// Create button if it is not created.
mWindowManager.mLayout = null;
mWindowManager.mHasSizeCompat = true;
+ doReturn(true).when(mWindowManager).shouldShowSizeCompatRestartButton(mTaskInfo);
mWindowManager.updateVisibility(/* canShow= */ true);
verify(mWindowManager).createLayout(/* canShow= */ true);
@@ -464,6 +476,37 @@ public class CompatUIWindowManagerTest extends ShellTestCase {
Assert.assertTrue(mWindowManager.needsToBeRecreated(newTaskInfo, mTaskListener));
}
+ @Test
+ public void testShouldShowSizeCompatRestartButton() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_ALLOW_HIDE_SCM_BUTTON);
+
+ doReturn(86).when(mCompatUIConfiguration).getHideSizeCompatRestartButtonTolerance();
+ mWindowManager = new CompatUIWindowManager(mContext, mTaskInfo, mSyncTransactionQueue,
+ mCallback, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
+ mCompatUIConfiguration, mOnRestartButtonClicked);
+
+ // Simulate rotation of activity in square display
+ TaskInfo taskInfo = createTaskInfo(true, CAMERA_COMPAT_CONTROL_HIDDEN);
+ taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 2000, 2000));
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 2000;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1850;
+
+ assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+
+ // Simulate exiting split screen/folding
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+ assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+
+ // Simulate folding
+ taskInfo.configuration.windowConfiguration.setBounds(new Rect(0, 0, 1000, 2000));
+ assertFalse(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+
+ taskInfo.appCompatTaskInfo.topActivityLetterboxWidth = 1000;
+ taskInfo.appCompatTaskInfo.topActivityLetterboxHeight = 500;
+ assertTrue(mWindowManager.shouldShowSizeCompatRestartButton(taskInfo));
+ }
+
private static TaskInfo createTaskInfo(boolean hasSizeCompat,
@AppCompatTaskInfo.CameraCompatControlState int cameraCompatControlState) {
ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
index 3bc90ade898e..be639e867e0b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt
@@ -34,6 +34,7 @@ import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
+import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.verifyZeroInteractions
import org.mockito.kotlin.whenever
@@ -150,6 +151,23 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
}
@Test
+ fun startDragToDesktop_anotherTransitionInProgress_startDropped() {
+ val task = createTask()
+ val dragAnimator = mock<MoveToDesktopAnimator>()
+
+ // Simulate attempt to start two drag to desktop transitions.
+ startDragToDesktopTransition(task, dragAnimator)
+ startDragToDesktopTransition(task, dragAnimator)
+
+ // Verify transition only started once.
+ verify(transitions, times(1)).startTransition(
+ eq(TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP),
+ any(),
+ eq(handler)
+ )
+ }
+
+ @Test
fun cancelDragToDesktop_startWasReady_cancel() {
val task = createTask()
val dragAnimator = mock<MoveToDesktopAnimator>()
@@ -189,6 +207,32 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() {
verifyZeroInteractions(dragAnimator)
}
+ @Test
+ fun cancelDragToDesktop_transitionNotInProgress_dropCancel() {
+ // Then cancel is called before the transition was started.
+ handler.cancelDragToDesktopTransition()
+
+ // Verify cancel is dropped.
+ verify(transitions, never()).startTransition(
+ eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP),
+ any(),
+ eq(handler)
+ )
+ }
+
+ @Test
+ fun finishDragToDesktop_transitionNotInProgress_dropFinish() {
+ // Then finish is called before the transition was started.
+ handler.finishDragToDesktopTransition(WindowContainerTransaction())
+
+ // Verify finish is dropped.
+ verify(transitions, never()).startTransition(
+ eq(TRANSIT_DESKTOP_MODE_END_DRAG_TO_DESKTOP),
+ any(),
+ eq(handler)
+ )
+ }
+
private fun startDragToDesktopTransition(
task: RunningTaskInfo,
dragAnimator: MoveToDesktopAnimator
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 08412101c30c..86253f35a51d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -144,13 +144,13 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
}
@Test
- fun testDragResize_noMove_showsResizeVeil() {
+ fun testDragResize_noMove_doesNotShowResizeVeil() {
taskPositioner.onDragPositioningStart(
CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
- verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS)
+ verify(mockDesktopWindowDecoration, never()).showResizeVeil(STARTING_BOUNDS)
taskPositioner.onDragPositioningEnd(
STARTING_BOUNDS.left.toFloat(),
@@ -162,7 +162,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
(change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
change.configuration.windowConfiguration.bounds == STARTING_BOUNDS}},
eq(taskPositioner))
- verify(mockDesktopWindowDecoration).hideResizeVeil()
+ verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
}
@Test
@@ -212,7 +212,6 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
STARTING_BOUNDS.right.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
- verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS)
taskPositioner.onDragPositioningMove(
STARTING_BOUNDS.right.toFloat() + 10,
@@ -222,6 +221,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
val rectAfterMove = Rect(STARTING_BOUNDS)
rectAfterMove.right += 10
rectAfterMove.top += 10
+ verify(mockDesktopWindowDecoration).showResizeVeil(rectAfterMove)
verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
@@ -237,7 +237,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
val rectAfterEnd = Rect(rectAfterMove)
rectAfterEnd.right += 10
rectAfterEnd.top += 10
- verify(mockDesktopWindowDecoration, times(2)).updateResizeVeil(any())
+ verify(mockDesktopWindowDecoration).updateResizeVeil(any())
verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
return@argThat wct.changes.any { (token, change) ->
token == taskBinder &&
@@ -253,7 +253,6 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() {
STARTING_BOUNDS.left.toFloat(),
STARTING_BOUNDS.top.toFloat()
)
- verify(mockDesktopWindowDecoration).showResizeVeil(STARTING_BOUNDS)
taskPositioner.onDragPositioningMove(
STARTING_BOUNDS.left.toFloat(),
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index c9d5e074271b..d9166a16cdea 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -21,6 +21,7 @@
#include <algorithm>
#include <cstddef>
#include <limits>
+#include <optional>
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
@@ -50,7 +51,9 @@ namespace {
// contiguous block of memory to store both the TypeSpec struct and
// the Type structs.
struct TypeSpecBuilder {
- explicit TypeSpecBuilder(incfs::verified_map_ptr<ResTable_typeSpec> header) : header_(header) {}
+ explicit TypeSpecBuilder(incfs::verified_map_ptr<ResTable_typeSpec> header) : header_(header) {
+ type_entries.reserve(dtohs(header_->typesCount));
+ }
void AddType(incfs::verified_map_ptr<ResTable_type> type) {
TypeSpec::TypeEntry& entry = type_entries.emplace_back();
@@ -59,6 +62,7 @@ struct TypeSpecBuilder {
}
TypeSpec Build() {
+ type_entries.shrink_to_fit();
return {header_, std::move(type_entries)};
}
@@ -450,7 +454,8 @@ const LoadedPackage* LoadedArsc::GetPackageById(uint8_t package_id) const {
std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
package_property_t property_flags) {
ATRACE_NAME("LoadedPackage::Load");
- std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage());
+ const bool optimize_name_lookups = (property_flags & PROPERTY_OPTIMIZE_NAME_LOOKUPS) != 0;
+ std::unique_ptr<LoadedPackage> loaded_package(new LoadedPackage(optimize_name_lookups));
// typeIdOffset was added at some point, but we still must recognize apps built before this
// was added.
@@ -499,7 +504,7 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
// A map of TypeSpec builders, each associated with an type index.
// We use these to accumulate the set of Types available for a TypeSpec, and later build a single,
// contiguous block of memory that holds all the Types together with the TypeSpec.
- std::unordered_map<int, std::unique_ptr<TypeSpecBuilder>> type_builder_map;
+ std::unordered_map<int, std::optional<TypeSpecBuilder>> type_builder_map;
ChunkIterator iter(chunk.data_ptr(), chunk.data_size());
while (iter.HasNext()) {
@@ -567,14 +572,14 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
return {};
}
- if (entry_count * sizeof(uint32_t) > chunk.data_size()) {
+ if (entry_count * sizeof(uint32_t) > child_chunk.data_size()) {
LOG(ERROR) << "RES_TABLE_TYPE_SPEC_TYPE too small to hold entries.";
return {};
}
- std::unique_ptr<TypeSpecBuilder>& builder_ptr = type_builder_map[type_spec->id];
- if (builder_ptr == nullptr) {
- builder_ptr = util::make_unique<TypeSpecBuilder>(type_spec.verified());
+ auto& maybe_type_builder = type_builder_map[type_spec->id];
+ if (!maybe_type_builder) {
+ maybe_type_builder.emplace(type_spec.verified());
loaded_package->resource_ids_.set(type_spec->id, entry_count);
} else {
LOG(WARNING) << StringPrintf("RES_TABLE_TYPE_SPEC_TYPE already defined for ID %02x",
@@ -594,9 +599,9 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
}
// Type chunks must be preceded by their TypeSpec chunks.
- std::unique_ptr<TypeSpecBuilder>& builder_ptr = type_builder_map[type->id];
- if (builder_ptr != nullptr) {
- builder_ptr->AddType(type.verified());
+ auto& maybe_type_builder = type_builder_map[type->id];
+ if (maybe_type_builder) {
+ maybe_type_builder->AddType(type.verified());
} else {
LOG(ERROR) << StringPrintf(
"RES_TABLE_TYPE_TYPE with ID %02x found without preceding RES_TABLE_TYPE_SPEC_TYPE.",
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 4c992becda7c..2c99f1aa3675 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -447,15 +447,19 @@ Res_png_9patch* Res_png_9patch::deserialize(void* inData)
// --------------------------------------------------------------------
// --------------------------------------------------------------------
-ResStringPool::ResStringPool()
- : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
-{
+ResStringPool::ResStringPool() : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL) {
}
-ResStringPool::ResStringPool(const void* data, size_t size, bool copyData)
- : mError(NO_INIT), mOwnedData(NULL), mHeader(NULL), mCache(NULL)
-{
- setTo(data, size, copyData);
+ResStringPool::ResStringPool(bool optimize_name_lookups) : ResStringPool() {
+ if (optimize_name_lookups) {
+ mIndexLookupCache.emplace();
+ }
+}
+
+ResStringPool::ResStringPool(const void* data, size_t size, bool copyData,
+ bool optimize_name_lookups)
+ : ResStringPool(optimize_name_lookups) {
+ setTo(data, size, copyData);
}
ResStringPool::~ResStringPool()
@@ -683,6 +687,14 @@ status_t ResStringPool::setTo(incfs::map_ptr<void> data, size_t size, bool copyD
mStylePoolSize = 0;
}
+ if (mIndexLookupCache) {
+ if ((mHeader->flags & ResStringPool_header::UTF8_FLAG) != 0) {
+ mIndexLookupCache->first.reserve(mHeader->stringCount);
+ } else {
+ mIndexLookupCache->second.reserve(mHeader->stringCount);
+ }
+ }
+
return (mError=NO_ERROR);
}
@@ -708,6 +720,10 @@ void ResStringPool::uninit()
free(mOwnedData);
mOwnedData = NULL;
}
+ if (mIndexLookupCache) {
+ mIndexLookupCache->first.clear();
+ mIndexLookupCache->second.clear();
+ }
}
/**
@@ -824,11 +840,11 @@ base::expected<StringPiece16, NullOrIOError> ResStringPool::stringAt(size_t idx)
// encLen must be less than 0x7FFF due to encoding.
if ((uint32_t)(u8str+*u8len-strings) < mStringPoolSize) {
- AutoMutex lock(mDecodeLock);
+ AutoMutex lock(mCachesLock);
- if (mCache != NULL && mCache[idx] != NULL) {
- return StringPiece16(mCache[idx], *u16len);
- }
+ if (mCache != NULL && mCache[idx] != NULL) {
+ return StringPiece16(mCache[idx], *u16len);
+ }
// Retrieve the actual length of the utf8 string if the
// encoded length was truncated
@@ -1093,12 +1109,24 @@ base::expected<size_t, NullOrIOError> ResStringPool::indexOfString(const char16_
// block, start searching at the back.
String8 str8(str, strLen);
const size_t str8Len = str8.size();
+ std::optional<AutoMutex> cacheLock;
+ if (mIndexLookupCache) {
+ cacheLock.emplace(mCachesLock);
+ if (auto it = mIndexLookupCache->first.find(std::string_view(str8));
+ it != mIndexLookupCache->first.end()) {
+ return it->second;
+ }
+ }
+
for (int i=mHeader->stringCount-1; i>=0; i--) {
const base::expected<StringPiece, NullOrIOError> s = string8At(i);
if (UNLIKELY(IsIOError(s))) {
return base::unexpected(s.error());
}
if (s.has_value()) {
+ if (mIndexLookupCache) {
+ mIndexLookupCache->first.insert({*s, i});
+ }
if (kDebugStringPoolNoisy) {
ALOGI("Looking at %s, i=%d\n", s->data(), i);
}
@@ -1151,20 +1179,32 @@ base::expected<size_t, NullOrIOError> ResStringPool::indexOfString(const char16_
// most often this happens because we want to get IDs for style
// span tags; since those always appear at the end of the string
// block, start searching at the back.
+ std::optional<AutoMutex> cacheLock;
+ if (mIndexLookupCache) {
+ cacheLock.emplace(mCachesLock);
+ if (auto it = mIndexLookupCache->second.find({str, strLen});
+ it != mIndexLookupCache->second.end()) {
+ return it->second;
+ }
+ }
for (int i=mHeader->stringCount-1; i>=0; i--) {
const base::expected<StringPiece16, NullOrIOError> s = stringAt(i);
if (UNLIKELY(IsIOError(s))) {
return base::unexpected(s.error());
}
if (kDebugStringPoolNoisy) {
- ALOGI("Looking at %s, i=%d\n", String8(s->data(), s->size()).c_str(), i);
+ ALOGI("Looking16 at %s, i=%d\n", String8(s->data(), s->size()).c_str(), i);
}
- if (s.has_value() && strLen == s->size() &&
- strzcmp16(s->data(), s->size(), str, strLen) == 0) {
+ if (s.has_value()) {
+ if (mIndexLookupCache) {
+ mIndexLookupCache->second.insert({*s, i});
+ }
+ if (strLen == s->size() && strzcmp16(s->data(), s->size(), str, strLen) == 0) {
if (kDebugStringPoolNoisy) {
- ALOGI("MATCH!");
+ ALOGI("MATCH16!");
}
return i;
+ }
}
}
}
diff --git a/libs/androidfw/include/androidfw/Idmap.h b/libs/androidfw/include/androidfw/Idmap.h
index 60689128dffb..d9f7c2a1ac19 100644
--- a/libs/androidfw/include/androidfw/Idmap.h
+++ b/libs/androidfw/include/androidfw/Idmap.h
@@ -71,7 +71,7 @@ class OverlayDynamicRefTable : public DynamicRefTable {
// Rewrites a compile-time overlay resource id to the runtime resource id of corresponding target
// resource.
- virtual status_t lookupResourceIdNoRewrite(uint32_t* resId) const;
+ status_t lookupResourceIdNoRewrite(uint32_t* resId) const;
const Idmap_data_header* data_header_;
const Idmap_overlay_entry* entries_;
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 3a7287187781..413b27829474 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -99,6 +99,9 @@ enum : package_property_t {
// The apk assets only contain the overlayable declarations information.
PROPERTY_ONLY_OVERLAYABLES = 1U << 5U,
+
+ // Optimize the resource lookups by name via an in-memory lookup table.
+ PROPERTY_OPTIMIZE_NAME_LOOKUPS = 1U << 6U,
};
struct OverlayableInfo {
@@ -285,7 +288,9 @@ class LoadedPackage {
private:
DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
- LoadedPackage() = default;
+ explicit LoadedPackage(bool optimize_name_lookups = false)
+ : type_string_pool_(optimize_name_lookups), key_string_pool_(optimize_name_lookups) {
+ }
ResStringPool type_string_pool_;
ResStringPool key_string_pool_;
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index c0514fdff469..3d1403d0e039 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -22,27 +22,28 @@
#include <android-base/expected.h>
#include <android-base/unique_fd.h>
-
+#include <android/configuration.h>
#include <androidfw/Asset.h>
#include <androidfw/Errors.h>
#include <androidfw/LocaleData.h>
#include <androidfw/StringPiece.h>
#include <utils/ByteOrder.h>
#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
#include <utils/String16.h>
#include <utils/Vector.h>
-#include <utils/KeyedVector.h>
-
#include <utils/threads.h>
#include <stdint.h>
#include <sys/types.h>
-#include <android/configuration.h>
-
#include <array>
#include <map>
#include <memory>
+#include <optional>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
namespace android {
@@ -512,23 +513,24 @@ struct ResStringPool_span
class ResStringPool
{
public:
- ResStringPool();
- ResStringPool(const void* data, size_t size, bool copyData=false);
- virtual ~ResStringPool();
+ ResStringPool();
+ explicit ResStringPool(bool optimize_name_lookups);
+ ResStringPool(const void* data, size_t size, bool copyData = false,
+ bool optimize_name_lookups = false);
+ virtual ~ResStringPool();
- void setToEmpty();
- status_t setTo(incfs::map_ptr<void> data, size_t size, bool copyData=false);
+ void setToEmpty();
+ status_t setTo(incfs::map_ptr<void> data, size_t size, bool copyData = false);
- status_t getError() const;
+ status_t getError() const;
- void uninit();
+ void uninit();
- // Return string entry as UTF16; if the pool is UTF8, the string will
- // be converted before returning.
- inline base::expected<StringPiece16, NullOrIOError> stringAt(
- const ResStringPool_ref& ref) const {
- return stringAt(ref.index);
- }
+ // Return string entry as UTF16; if the pool is UTF8, the string will
+ // be converted before returning.
+ inline base::expected<StringPiece16, NullOrIOError> stringAt(const ResStringPool_ref& ref) const {
+ return stringAt(ref.index);
+ }
virtual base::expected<StringPiece16, NullOrIOError> stringAt(size_t idx) const;
// Note: returns null if the string pool is not UTF8.
@@ -557,7 +559,7 @@ private:
void* mOwnedData;
incfs::verified_map_ptr<ResStringPool_header> mHeader;
size_t mSize;
- mutable Mutex mDecodeLock;
+ mutable Mutex mCachesLock;
incfs::map_ptr<uint32_t> mEntries;
incfs::map_ptr<uint32_t> mEntryStyles;
incfs::map_ptr<void> mStrings;
@@ -566,6 +568,10 @@ private:
incfs::map_ptr<uint32_t> mStyles;
uint32_t mStylePoolSize; // number of uint32_t
+ mutable std::optional<std::pair<std::unordered_map<std::string_view, int>,
+ std::unordered_map<std::u16string_view, int>>>
+ mIndexLookupCache;
+
base::expected<StringPiece, NullOrIOError> stringDecodeAt(
size_t idx, incfs::map_ptr<uint8_t> str, size_t encLen) const;
};
@@ -1401,8 +1407,8 @@ struct ResTable_typeSpec
// Must be 0.
uint8_t res0;
- // Must be 0.
- uint16_t res1;
+ // Used to be reserved, if >0 specifies the number of ResTable_type entries for this spec.
+ uint16_t typesCount;
// Number of uint32_t entry configuration masks that follow.
uint32_t entryCount;
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index eebf8aabd89c..b40b73c111d0 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -716,7 +716,6 @@ cc_test {
],
shared_libs: [
"libmemunreachable",
- "server_configurable_flags",
],
srcs: [
"tests/unit/main.cpp",
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index d4af0872e31e..a5a841e07d7a 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -23,6 +23,7 @@
#include <mutex>
+#include "Properties.h"
#include "utils/Macros.h"
namespace android {
@@ -60,7 +61,13 @@ public:
static void setWideColorDataspace(ADataSpace dataspace);
static void setSupportFp16ForHdr(bool supportFp16ForHdr);
- static bool isSupportFp16ForHdr() { return get()->mSupportFp16ForHdr; };
+ static bool isSupportFp16ForHdr() {
+ if (!Properties::hdr10bitPlus) {
+ return false;
+ }
+
+ return get()->mSupportFp16ForHdr;
+ };
static void setSupportMixedColorSpaces(bool supportMixedColorSpaces);
static bool isSupportMixedColorSpaces() { return get()->mSupportMixedColorSpaces; };
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index 00d049cde925..6ebfc63bdf26 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -41,6 +41,14 @@ inline bool deprecate_ui_fonts() {
#endif // __ANDROID__
}
+inline bool inter_character_justification() {
+#ifdef __ANDROID__
+ return com_android_text_flags_inter_character_justification();
+#else
+ return true;
+#endif // __ANDROID__
+}
+
} // namespace text_feature
} // namespace android
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index 16de21def0e3..71f7926930fc 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -379,7 +379,7 @@ static FormatInfo determineFormat(const SkBitmap& skBitmap, bool usingGL) {
case kAlpha_8_SkColorType:
formatInfo.isSupported = HardwareBitmapUploader::hasAlpha8Support();
formatInfo.bufferFormat = AHARDWAREBUFFER_FORMAT_R8_UNORM;
- formatInfo.format = GL_R8;
+ formatInfo.format = GL_RED;
formatInfo.type = GL_UNSIGNED_BYTE;
formatInfo.vkFormat = VK_FORMAT_R8_UNORM;
break;
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index d58c872dbc56..755332ff66fd 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -38,6 +38,9 @@ namespace hwui_flags {
constexpr bool clip_surfaceviews() {
return false;
}
+constexpr bool hdr_10bit_plus() {
+ return false;
+}
} // namespace hwui_flags
#endif
@@ -105,6 +108,7 @@ bool Properties::isSystemOrPersistent = false;
float Properties::maxHdrHeadroomOn8bit = 5.f; // TODO: Refine this number
bool Properties::clipSurfaceViews = false;
+bool Properties::hdr10bitPlus = false;
StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
@@ -177,6 +181,7 @@ bool Properties::load() {
clipSurfaceViews =
base::GetBoolProperty("debug.hwui.clip_surfaceviews", hwui_flags::clip_surfaceviews());
+ hdr10bitPlus = hwui_flags::hdr_10bit_plus();
return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw);
}
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index b956facf6f90..ec53070f6cb8 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -336,6 +336,7 @@ public:
static float maxHdrHeadroomOn8bit;
static bool clipSurfaceViews;
+ static bool hdr10bitPlus;
static StretchEffectBehavior getStretchEffectBehavior() {
return stretchEffectBehavior;
diff --git a/libs/hwui/hwui/MinikinSkia.cpp b/libs/hwui/hwui/MinikinSkia.cpp
index f4ee36ec66d1..bbb142014ed8 100644
--- a/libs/hwui/hwui/MinikinSkia.cpp
+++ b/libs/hwui/hwui/MinikinSkia.cpp
@@ -131,11 +131,6 @@ std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation(
const std::vector<minikin::FontVariation>& variations) const {
SkFontArguments args;
- int ttcIndex;
- std::unique_ptr<SkStreamAsset> stream(mTypeface->openStream(&ttcIndex));
- LOG_ALWAYS_FATAL_IF(stream == nullptr, "openStream failed");
-
- args.setCollectionIndex(ttcIndex);
std::vector<SkFontArguments::VariationPosition::Coordinate> skVariation;
skVariation.resize(variations.size());
for (size_t i = 0; i < variations.size(); i++) {
@@ -143,11 +138,10 @@ std::shared_ptr<minikin::MinikinFont> MinikinFontSkia::createFontWithVariation(
skVariation[i].value = SkFloatToScalar(variations[i].value);
}
args.setVariationDesignPosition({skVariation.data(), static_cast<int>(skVariation.size())});
- sk_sp<SkFontMgr> fm = android::FreeTypeFontMgr();
- sk_sp<SkTypeface> face(fm->makeFromStream(std::move(stream), args));
+ sk_sp<SkTypeface> face = mTypeface->makeClone(args);
return std::make_shared<MinikinFontSkia>(std::move(face), mSourceId, mFontData, mFontSize,
- mFilePath, ttcIndex, variations);
+ mFilePath, mTtcIndex, variations);
}
// hinting<<16 | edging<<8 | bools:5bits
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 833069f363c8..56133699d5f5 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -72,10 +72,13 @@ minikin::Layout MinikinUtils::doLayout(const Paint* paint, minikin::Bidi bidiFla
const minikin::Range contextRange(contextStart, contextStart + contextCount);
const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
+ const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+ ? paint->getRunFlag()
+ : minikin::RunFlag::NONE;
if (mt == nullptr) {
return minikin::Layout(textBuf.substr(contextRange), range - contextStart, bidiFlags,
- minikinPaint, startHyphen, endHyphen);
+ minikinPaint, startHyphen, endHyphen, minikinRunFlag);
} else {
return mt->buildLayout(textBuf, range, contextRange, minikinPaint, startHyphen, endHyphen);
}
@@ -102,9 +105,12 @@ float MinikinUtils::measureText(const Paint* paint, minikin::Bidi bidiFlags,
const minikin::Range range(start, start + count);
const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
+ const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+ ? paint->getRunFlag()
+ : minikin::RunFlag::NONE;
return minikin::Layout::measureText(textBuf, range, bidiFlags, minikinPaint, startHyphen,
- endHyphen, advances, bounds, clusterCount);
+ endHyphen, advances, bounds, clusterCount, minikinRunFlag);
}
minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index ef4dce57bf46..708f96e5a070 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -25,6 +25,7 @@
#include <minikin/FontFamily.h>
#include <minikin/FontFeature.h>
#include <minikin/Hyphenator.h>
+#include <minikin/Layout.h>
#include <string>
@@ -144,6 +145,9 @@ public:
bool isDevKern() const { return mDevKern; }
void setDevKern(bool d) { mDevKern = d; }
+ minikin::RunFlag getRunFlag() const { return mRunFlag; }
+ void setRunFlag(minikin::RunFlag runFlag) { mRunFlag = runFlag; }
+
// Deprecated -- bitmapshaders will be taking this flag explicitly
bool isFilterBitmap() const { return mFilterBitmap; }
void setFilterBitmap(bool filter) { mFilterBitmap = filter; }
@@ -188,6 +192,7 @@ private:
bool mStrikeThru = false;
bool mUnderline = false;
bool mDevKern = false;
+ minikin::RunFlag mRunFlag = minikin::RunFlag::NONE;
};
} // namespace android
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index aac928f85924..c32ea01db7b7 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -47,8 +47,8 @@ Paint::Paint(const Paint& paint)
, mFilterBitmap(paint.mFilterBitmap)
, mStrikeThru(paint.mStrikeThru)
, mUnderline(paint.mUnderline)
- , mDevKern(paint.mDevKern) {}
-
+ , mDevKern(paint.mDevKern)
+ , mRunFlag(paint.mRunFlag) {}
Paint::~Paint() {}
@@ -68,21 +68,19 @@ Paint& Paint::operator=(const Paint& other) {
mStrikeThru = other.mStrikeThru;
mUnderline = other.mUnderline;
mDevKern = other.mDevKern;
+ mRunFlag = other.mRunFlag;
return *this;
}
bool operator==(const Paint& a, const Paint& b) {
- return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) &&
- a.mFont == b.mFont &&
- a.mLooper == b.mLooper &&
- a.mLetterSpacing == b.mLetterSpacing && a.mWordSpacing == b.mWordSpacing &&
- a.mFontFeatureSettings == b.mFontFeatureSettings &&
+ return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) && a.mFont == b.mFont &&
+ a.mLooper == b.mLooper && a.mLetterSpacing == b.mLetterSpacing &&
+ a.mWordSpacing == b.mWordSpacing && a.mFontFeatureSettings == b.mFontFeatureSettings &&
a.mMinikinLocaleListId == b.mMinikinLocaleListId &&
a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit &&
a.mTypeface == b.mTypeface && a.mAlign == b.mAlign &&
- a.mFilterBitmap == b.mFilterBitmap &&
- a.mStrikeThru == b.mStrikeThru && a.mUnderline == b.mUnderline &&
- a.mDevKern == b.mDevKern;
+ a.mFilterBitmap == b.mFilterBitmap && a.mStrikeThru == b.mStrikeThru &&
+ a.mUnderline == b.mUnderline && a.mDevKern == b.mDevKern && a.mRunFlag == b.mRunFlag;
}
void Paint::reset() {
@@ -96,6 +94,7 @@ void Paint::reset() {
mStrikeThru = false;
mUnderline = false;
mDevKern = false;
+ mRunFlag = minikin::RunFlag::NONE;
}
void Paint::setLooper(sk_sp<BlurDrawLooper> looper) {
@@ -133,6 +132,8 @@ static const uint32_t sForceAutoHinting = 0x800;
// flags related to minikin::Paint
static const uint32_t sUnderlineFlag = 0x08;
static const uint32_t sStrikeThruFlag = 0x10;
+static const uint32_t sTextRunLeftEdge = 0x2000;
+static const uint32_t sTextRunRightEdge = 0x4000;
// flags no longer supported on native side (but mirrored for compatibility)
static const uint32_t sDevKernFlag = 0x100;
@@ -186,6 +187,12 @@ uint32_t Paint::getJavaFlags() const {
flags |= -(int)mUnderline & sUnderlineFlag;
flags |= -(int)mDevKern & sDevKernFlag;
flags |= -(int)mFilterBitmap & sFilterBitmapFlag;
+ if (mRunFlag & minikin::RunFlag::LEFT_EDGE) {
+ flags |= sTextRunLeftEdge;
+ }
+ if (mRunFlag & minikin::RunFlag::RIGHT_EDGE) {
+ flags |= sTextRunRightEdge;
+ }
return flags;
}
@@ -196,6 +203,15 @@ void Paint::setJavaFlags(uint32_t flags) {
mUnderline = (flags & sUnderlineFlag) != 0;
mDevKern = (flags & sDevKernFlag) != 0;
mFilterBitmap = (flags & sFilterBitmapFlag) != 0;
+
+ std::underlying_type<minikin::RunFlag>::type rawFlag = minikin::RunFlag::NONE;
+ if (flags & sTextRunLeftEdge) {
+ rawFlag |= minikin::RunFlag::LEFT_EDGE;
+ }
+ if (flags & sTextRunRightEdge) {
+ rawFlag |= minikin::RunFlag::RIGHT_EDGE;
+ }
+ mRunFlag = static_cast<minikin::RunFlag>(rawFlag);
}
} // namespace android
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 8ba750372d18..d5725935551a 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -613,6 +613,12 @@ static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray c
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
const Typeface* typeface = paint->getAndroidTypeface();
ScopedCharArrayRO text(env, charArray);
+
+ // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+ // text as entire line mode.
+ const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+ paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
// drawTextString and drawTextChars doesn't use context info
get_canvas(canvasHandle)->drawText(
text.get() + index, count, // text buffer
@@ -620,6 +626,7 @@ static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray c
0, count, // context range
x, y, // draw position
static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */);
+ paint->setRunFlag(originalRunFlag);
}
static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring strObj,
@@ -629,6 +636,12 @@ static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring str
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
const Typeface* typeface = paint->getAndroidTypeface();
const int count = end - start;
+
+ // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+ // text as entire line mode.
+ const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+ paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
// drawTextString and drawTextChars doesn't use context info
get_canvas(canvasHandle)->drawText(
text.get() + start, count, // text buffer
@@ -636,6 +649,7 @@ static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring str
0, count, // context range
x, y, // draw position
static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */);
+ paint->setRunFlag(originalRunFlag);
}
static void drawTextRunChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray charArray,
@@ -681,9 +695,15 @@ static void drawTextOnPathChars(JNIEnv* env, jobject, jlong canvasHandle, jcharA
jchar* jchars = env->GetCharArrayElements(text, NULL);
+ // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+ // text as entire line mode.
+ const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+ paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
get_canvas(canvasHandle)->drawTextOnPath(jchars + index, count,
static_cast<minikin::Bidi>(bidiFlags), *path, hOffset, vOffset, *paint, typeface);
+ paint->setRunFlag(originalRunFlag);
env->ReleaseCharArrayElements(text, jchars, 0);
}
@@ -697,9 +717,15 @@ static void drawTextOnPathString(JNIEnv* env, jobject, jlong canvasHandle, jstri
const jchar* jchars = env->GetStringChars(text, NULL);
int count = env->GetStringLength(text);
+ // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+ // text as entire line mode.
+ const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+ paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
get_canvas(canvasHandle)->drawTextOnPath(jchars, count, static_cast<minikin::Bidi>(bidiFlags),
*path, hOffset, vOffset, *paint, typeface);
+ paint->setRunFlag(originalRunFlag);
env->ReleaseStringChars(text, jchars);
}
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index c5ffbb70213e..c8d598702a7c 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -118,7 +118,7 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
- const HardwareBufferRenderParams& bufferParams) {
+ const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) {
if (!isCapturingSkp() && !mHardwareBuffer) {
mEglManager.damageFrame(frame, dirty);
}
@@ -171,6 +171,7 @@ IRenderPipeline::DrawResult SkiaOpenGLPipeline::draw(
// Draw visual debugging features
if (CC_UNLIKELY(Properties::showDirtyRegions ||
ProfileType::None != Properties::getProfileType())) {
+ std::scoped_lock lock(profilerLock);
SkCanvas* profileCanvas = surface->getCanvas();
SkiaProfileRenderer profileRenderer(profileCanvas, frame.width(), frame.height());
profiler->draw(profileRenderer);
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
index 098a74655831..ebe8b6e15d44 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.h
@@ -42,7 +42,8 @@ public:
const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler,
- const renderthread::HardwareBufferRenderParams& bufferParams) override;
+ const renderthread::HardwareBufferRenderParams& bufferParams,
+ std::mutex& profilerLock) override;
GrSurfaceOrigin getSurfaceOrigin() override { return kBottomLeft_GrSurfaceOrigin; }
bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
const SkRect& screenDirty, FrameInfo* currentFrameInfo,
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index e0f1f6ef44be..326b6ed77fe0 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -650,9 +650,14 @@ void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
mSurfaceColorSpace = DeviceInfo::get()->getWideColorSpace();
break;
case ColorMode::Hdr:
- mSurfaceColorType = SkColorType::kN32_SkColorType;
- mSurfaceColorSpace = SkColorSpace::MakeRGB(
- GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
+ if (DeviceInfo::get()->isSupportFp16ForHdr()) {
+ mSurfaceColorType = SkColorType::kRGBA_F16_SkColorType;
+ mSurfaceColorSpace = SkColorSpace::MakeSRGB();
+ } else {
+ mSurfaceColorType = SkColorType::kN32_SkColorType;
+ mSurfaceColorSpace = SkColorSpace::MakeRGB(
+ GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
+ }
break;
case ColorMode::Hdr10:
mSurfaceColorType = SkColorType::kRGBA_1010102_SkColorType;
@@ -669,8 +674,13 @@ void SkiaPipeline::setSurfaceColorProperties(ColorMode colorMode) {
void SkiaPipeline::setTargetSdrHdrRatio(float ratio) {
if (mColorMode == ColorMode::Hdr || mColorMode == ColorMode::Hdr10) {
mTargetSdrHdrRatio = ratio;
- mSurfaceColorSpace = SkColorSpace::MakeRGB(GetExtendedTransferFunction(mTargetSdrHdrRatio),
- SkNamedGamut::kDisplayP3);
+
+ if (mColorMode == ColorMode::Hdr && DeviceInfo::get()->isSupportFp16ForHdr()) {
+ mSurfaceColorSpace = SkColorSpace::MakeSRGB();
+ } else {
+ mSurfaceColorSpace = SkColorSpace::MakeRGB(
+ GetExtendedTransferFunction(mTargetSdrHdrRatio), SkNamedGamut::kDisplayP3);
+ }
} else {
mTargetSdrHdrRatio = 1.f;
}
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
index d74748936d15..fd0a8e06f39c 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
@@ -75,7 +75,7 @@ IRenderPipeline::DrawResult SkiaVulkanPipeline::draw(
const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler,
- const HardwareBufferRenderParams& bufferParams) {
+ const HardwareBufferRenderParams& bufferParams, std::mutex& profilerLock) {
sk_sp<SkSurface> backBuffer;
SkMatrix preTransform;
if (mHardwareBuffer) {
@@ -103,6 +103,7 @@ IRenderPipeline::DrawResult SkiaVulkanPipeline::draw(
// Draw visual debugging features
if (CC_UNLIKELY(Properties::showDirtyRegions ||
ProfileType::None != Properties::getProfileType())) {
+ std::scoped_lock lock(profilerLock);
SkCanvas* profileCanvas = backBuffer->getCanvas();
SkAutoCanvasRestore saver(profileCanvas, true);
profileCanvas->concat(preTransform);
diff --git a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
index e2ea57d32cd5..624eaa51a584 100644
--- a/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
+++ b/libs/hwui/pipeline/skia/SkiaVulkanPipeline.h
@@ -42,7 +42,8 @@ public:
const LightGeometry& lightGeometry, LayerUpdateQueue* layerUpdateQueue,
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode> >& renderNodes, FrameInfoVisualizer* profiler,
- const renderthread::HardwareBufferRenderParams& bufferParams) override;
+ const renderthread::HardwareBufferRenderParams& bufferParams,
+ std::mutex& profilerLock) override;
GrSurfaceOrigin getSurfaceOrigin() override { return kTopLeft_GrSurfaceOrigin; }
bool swapBuffers(const renderthread::Frame& frame, IRenderPipeline::DrawResult& drawResult,
const SkRect& screenDirty, FrameInfo* currentFrameInfo,
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index eb4d4948dc53..ac2a9366a1f6 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -124,24 +124,19 @@ void CacheManager::trimMemory(TrimLevel mode) {
// flush and submit all work to the gpu and wait for it to finish
mGrContext->flushAndSubmit(GrSyncCpu::kYes);
- switch (mode) {
- case TrimLevel::BACKGROUND:
- mGrContext->freeGpuResources();
- SkGraphics::PurgeAllCaches();
- mRenderThread.destroyRenderingContext();
- break;
- case TrimLevel::UI_HIDDEN:
- // Here we purge all the unlocked scratch resources and then toggle the resources cache
- // limits between the background and max amounts. This causes the unlocked resources
- // that have persistent data to be purged in LRU order.
- mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
- SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
- mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
- mGrContext->setResourceCacheLimit(mMaxResourceBytes);
- SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
- break;
- default:
- break;
+ if (mode >= TrimLevel::BACKGROUND) {
+ mGrContext->freeGpuResources();
+ SkGraphics::PurgeAllCaches();
+ mRenderThread.destroyRenderingContext();
+ } else if (mode == TrimLevel::UI_HIDDEN) {
+ // Here we purge all the unlocked scratch resources and then toggle the resources cache
+ // limits between the background and max amounts. This causes the unlocked resources
+ // that have persistent data to be purged in LRU order.
+ mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
+ SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
+ mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
+ mGrContext->setResourceCacheLimit(mMaxResourceBytes);
+ SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
}
}
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 56b52dc2abe7..9c7f7cc24266 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -625,14 +625,9 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) {
{
// FrameInfoVisualizer accesses the frame events, which cannot be mutated mid-draw
// or it can lead to memory corruption.
- // This lock is overly broad, but it's the quickest fix since this mutex is otherwise
- // not visible to IRenderPipeline much less FrameInfoVisualizer. And since this is
- // the thread we're primarily concerned about being responsive, this being too broad
- // shouldn't pose a performance issue.
- std::scoped_lock lock(mFrameMetricsReporterMutex);
- drawResult = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry,
- &mLayerUpdateQueue, mContentDrawBounds, mOpaque,
- mLightInfo, mRenderNodes, &(profiler()), mBufferParams);
+ drawResult = mRenderPipeline->draw(
+ frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, mContentDrawBounds,
+ mOpaque, mLightInfo, mRenderNodes, &(profiler()), mBufferParams, profilerLock());
}
uint64_t frameCompleteNr = getFrameNumber();
@@ -762,7 +757,7 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) {
mCurrentFrameInfo->markFrameCompleted();
mCurrentFrameInfo->set(FrameInfoIndex::GpuCompleted)
= mCurrentFrameInfo->get(FrameInfoIndex::FrameCompleted);
- std::scoped_lock lock(mFrameMetricsReporterMutex);
+ std::scoped_lock lock(mFrameInfoMutex);
mJankTracker.finishFrame(*mCurrentFrameInfo, mFrameMetricsReporter, frameCompleteNr,
mSurfaceControlGenerationId);
}
@@ -791,7 +786,7 @@ void CanvasContext::draw(bool solelyTextureViewUpdates) {
void CanvasContext::reportMetricsWithPresentTime() {
{ // acquire lock
- std::scoped_lock lock(mFrameMetricsReporterMutex);
+ std::scoped_lock lock(mFrameInfoMutex);
if (mFrameMetricsReporter == nullptr) {
return;
}
@@ -826,7 +821,7 @@ void CanvasContext::reportMetricsWithPresentTime() {
forthBehind->set(FrameInfoIndex::DisplayPresentTime) = presentTime;
{ // acquire lock
- std::scoped_lock lock(mFrameMetricsReporterMutex);
+ std::scoped_lock lock(mFrameInfoMutex);
if (mFrameMetricsReporter != nullptr) {
mFrameMetricsReporter->reportFrameMetrics(forthBehind->data(), true /*hasPresentTime*/,
frameNumber, surfaceControlId);
@@ -835,7 +830,7 @@ void CanvasContext::reportMetricsWithPresentTime() {
}
void CanvasContext::addFrameMetricsObserver(FrameMetricsObserver* observer) {
- std::scoped_lock lock(mFrameMetricsReporterMutex);
+ std::scoped_lock lock(mFrameInfoMutex);
if (mFrameMetricsReporter.get() == nullptr) {
mFrameMetricsReporter.reset(new FrameMetricsReporter());
}
@@ -849,7 +844,7 @@ void CanvasContext::addFrameMetricsObserver(FrameMetricsObserver* observer) {
}
void CanvasContext::removeFrameMetricsObserver(FrameMetricsObserver* observer) {
- std::scoped_lock lock(mFrameMetricsReporterMutex);
+ std::scoped_lock lock(mFrameInfoMutex);
if (mFrameMetricsReporter.get() != nullptr) {
mFrameMetricsReporter->removeObserver(observer);
if (!mFrameMetricsReporter->hasObservers()) {
@@ -886,7 +881,7 @@ void CanvasContext::onSurfaceStatsAvailable(void* context, int32_t surfaceContro
FrameInfo* frameInfo = instance->getFrameInfoFromLast4(frameNumber, surfaceControlId);
if (frameInfo != nullptr) {
- std::scoped_lock lock(instance->mFrameMetricsReporterMutex);
+ std::scoped_lock lock(instance->mFrameInfoMutex);
frameInfo->set(FrameInfoIndex::FrameCompleted) = std::max(gpuCompleteTime,
frameInfo->get(FrameInfoIndex::SwapBuffersCompleted));
frameInfo->set(FrameInfoIndex::GpuCompleted) = std::max(
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index be9b649030ae..e2e3fa35b9b0 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -164,6 +164,7 @@ public:
void notifyFramePending();
FrameInfoVisualizer& profiler() { return mProfiler; }
+ std::mutex& profilerLock() { return mFrameInfoMutex; }
void dumpFrames(int fd);
void resetFrameStats();
@@ -342,9 +343,8 @@ private:
std::string mName;
JankTracker mJankTracker;
FrameInfoVisualizer mProfiler;
- std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter
- GUARDED_BY(mFrameMetricsReporterMutex);
- std::mutex mFrameMetricsReporterMutex;
+ std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter GUARDED_BY(mFrameInfoMutex);
+ std::mutex mFrameInfoMutex;
std::set<RenderNode*> mPrefetchedLayers;
diff --git a/libs/hwui/renderthread/EglManager.cpp b/libs/hwui/renderthread/EglManager.cpp
index facf30b83b07..2904dfe76f40 100644
--- a/libs/hwui/renderthread/EglManager.cpp
+++ b/libs/hwui/renderthread/EglManager.cpp
@@ -441,22 +441,32 @@ Result<EGLSurface, EGLint> EglManager::createSurface(EGLNativeWindowType window,
colorMode = ColorMode::Default;
}
- if (DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType) {
+ // TODO: maybe we want to get rid of the WCG check if overlay properties just works?
+ const bool canUseFp16 = DeviceInfo::get()->isSupportFp16ForHdr() ||
+ DeviceInfo::get()->getWideColorType() == kRGBA_F16_SkColorType;
+
+ if (canUseFp16) {
if (mEglConfigF16 == EGL_NO_CONFIG_KHR) {
colorMode = ColorMode::Default;
} else {
config = mEglConfigF16;
}
}
+
if (EglExtensions.glColorSpace) {
attribs[0] = EGL_GL_COLORSPACE_KHR;
switch (colorMode) {
case ColorMode::Default:
attribs[1] = EGL_GL_COLORSPACE_LINEAR_KHR;
break;
+ case ColorMode::Hdr:
+ if (canUseFp16) {
+ attribs[1] = EGL_GL_COLORSPACE_SCRGB_EXT;
+ break;
+ // No fp16 support so fallthrough to HDR10
+ }
// We don't have an EGL colorspace for extended range P3 that's used for HDR
// So override it after configuring the EGL context
- case ColorMode::Hdr:
case ColorMode::Hdr10:
overrideWindowDataSpaceForHdr = true;
attribs[1] = EGL_GL_COLORSPACE_DISPLAY_P3_PASSTHROUGH_EXT;
diff --git a/libs/hwui/renderthread/IRenderPipeline.h b/libs/hwui/renderthread/IRenderPipeline.h
index 9c879d5a58d1..b8c3a4de2bd4 100644
--- a/libs/hwui/renderthread/IRenderPipeline.h
+++ b/libs/hwui/renderthread/IRenderPipeline.h
@@ -68,7 +68,8 @@ public:
const Rect& contentDrawBounds, bool opaque, const LightInfo& lightInfo,
const std::vector<sp<RenderNode>>& renderNodes,
FrameInfoVisualizer* profiler,
- const HardwareBufferRenderParams& bufferParams) = 0;
+ const HardwareBufferRenderParams& bufferParams,
+ std::mutex& profilerLock) = 0;
virtual bool swapBuffers(const Frame& frame, IRenderPipeline::DrawResult&,
const SkRect& screenDirty, FrameInfo* currentFrameInfo,
bool* requireSwap) = 0;
diff --git a/media/java/android/media/AudioFormat.java b/media/java/android/media/AudioFormat.java
index b002bbf20c08..ea136edf46be 100644
--- a/media/java/android/media/AudioFormat.java
+++ b/media/java/android/media/AudioFormat.java
@@ -1079,7 +1079,7 @@ public final class AudioFormat implements Parcelable {
* @return one of the values that can be set in {@link Builder#setEncoding(int)} or
* {@link AudioFormat#ENCODING_INVALID} if not set.
*/
- public int getEncoding() {
+ public @Encoding int getEncoding() {
return mEncoding;
}
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index 46db77708521..587e35b4b1fc 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -16,6 +16,9 @@
package android.media;
+import static com.android.media.codec.flags.Flags.FLAG_CODEC_IMPORTANCE;
+
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -1635,6 +1638,34 @@ public final class MediaFormat {
*/
public static final String KEY_ALLOW_FRAME_DROP = "allow-frame-drop";
+ /**
+ * A key describing the desired codec importance for the application.
+ * <p>
+ * The associated value is a positive integer including zero.
+ * Higher value means lesser importance.
+ * <p>
+ * The resource manager may use the codec importance, along with other factors
+ * when reclaiming codecs from an application.
+ * The specifics of reclaim policy is device dependent, but specifying the codec importance,
+ * will allow the resource manager to prioritize reclaiming less important codecs
+ * (assigned higher values) from the (reclaim) requesting application first.
+ * So, the codec importance is only relevant within the context of that application.
+ * <p>
+ * The codec importance can be set:
+ * <ul>
+ * <li>through {@link MediaCodec#configure}. </li>
+ * <li>through {@link MediaCodec#setParameters} if the codec has been configured already,
+ * which allows the users to change the codec importance multiple times.
+ * </ul>
+ * Any change/update in codec importance is guaranteed upon the completion of the function call
+ * that sets the codec importance. So, in case of concurrent codec operations,
+ * make sure to wait for the change in codec importance, before using another codec.
+ * Note that unless specified, by default the codecs will have highest importance (of value 0).
+ *
+ */
+ @FlaggedApi(FLAG_CODEC_IMPORTANCE)
+ public static final String KEY_IMPORTANCE = "importance";
+
/* package private */ MediaFormat(@NonNull Map<String, Object> map) {
mMap = map;
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 9616b5d44540..62412df97043 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -734,7 +734,7 @@ public final class MediaRouter2 {
* request.
* @param transferInitiatorPackageName the package name of the app that initiated the transfer.
* This value is used with the user handle to populate {@link
- * RoutingController#wasTransferRequestedBySelf()}.
+ * RoutingController#wasTransferInitiatedBySelf()}.
* @hide
*/
@FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
@@ -1550,11 +1550,11 @@ public final class MediaRouter2 {
}
/**
- * Returns whether the transfer was requested by the calling app (as determined by comparing
+ * Returns whether the transfer was initiated by the calling app (as determined by comparing
* {@link UserHandle} and package name).
*/
@FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
- public boolean wasTransferRequestedBySelf() {
+ public boolean wasTransferInitiatedBySelf() {
RoutingSessionInfo sessionInfo = getRoutingSessionInfo();
UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle();
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 7f9588644052..df9ecdc98e85 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -83,3 +83,10 @@ flag {
description: "Notify ActivityManager with the changes in playback state of the media session."
bug: "295518668"
}
+
+flag {
+ name: "enable_prevention_of_keep_alive_route_providers"
+ namespace: "media_solutions"
+ description: "Enables mechanisms to prevent route providers from keeping malicious apps alive."
+ bug: "263520343"
+}
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index 8ce1b6d5264d..3d927d36a369 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -121,7 +121,7 @@ interface IMediaProjectionManager {
@EnforcePermission("MANAGE_MEDIA_PROJECTION")
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
- void addCallback(IMediaProjectionWatcherCallback callback);
+ MediaProjectionInfo addCallback(IMediaProjectionWatcherCallback callback);
@JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+ ".permission.MANAGE_MEDIA_PROJECTION)")
diff --git a/media/java/android/media/projection/MediaProjectionInfo.java b/media/java/android/media/projection/MediaProjectionInfo.java
index ff608565d2e5..c82039297d6e 100644
--- a/media/java/android/media/projection/MediaProjectionInfo.java
+++ b/media/java/android/media/projection/MediaProjectionInfo.java
@@ -16,6 +16,7 @@
package android.media.projection;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
@@ -26,15 +27,18 @@ import java.util.Objects;
public final class MediaProjectionInfo implements Parcelable {
private final String mPackageName;
private final UserHandle mUserHandle;
+ private final IBinder mLaunchCookie;
- public MediaProjectionInfo(String packageName, UserHandle handle) {
+ public MediaProjectionInfo(String packageName, UserHandle handle, IBinder launchCookie) {
mPackageName = packageName;
mUserHandle = handle;
+ mLaunchCookie = launchCookie;
}
public MediaProjectionInfo(Parcel in) {
mPackageName = in.readString();
mUserHandle = UserHandle.readFromParcel(in);
+ mLaunchCookie = in.readStrongBinder();
}
public String getPackageName() {
@@ -45,12 +49,16 @@ public final class MediaProjectionInfo implements Parcelable {
return mUserHandle;
}
+ public IBinder getLaunchCookie() {
+ return mLaunchCookie;
+ }
+
@Override
public boolean equals(Object o) {
- if (o instanceof MediaProjectionInfo) {
- final MediaProjectionInfo other = (MediaProjectionInfo) o;
+ if (o instanceof MediaProjectionInfo other) {
return Objects.equals(other.mPackageName, mPackageName)
- && Objects.equals(other.mUserHandle, mUserHandle);
+ && Objects.equals(other.mUserHandle, mUserHandle)
+ && Objects.equals(other.mLaunchCookie, mLaunchCookie);
}
return false;
}
@@ -64,7 +72,8 @@ public final class MediaProjectionInfo implements Parcelable {
public String toString() {
return "MediaProjectionInfo{mPackageName="
+ mPackageName + ", mUserHandle="
- + mUserHandle + "}";
+ + mUserHandle + ", mLaunchCookie"
+ + mLaunchCookie + "}";
}
@Override
@@ -76,6 +85,7 @@ public final class MediaProjectionInfo implements Parcelable {
public void writeToParcel(Parcel out, int flags) {
out.writeString(mPackageName);
UserHandle.writeToParcel(mUserHandle, out);
+ out.writeStrongBinder(mLaunchCookie);
}
public static final @android.annotation.NonNull Parcelable.Creator<MediaProjectionInfo> CREATOR =
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index a8ffd2b4dd8f..2f6575e65077 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -149,4 +149,7 @@ interface ITvInputManager {
// For CTS purpose only. Add/remove a TvInputHardware device
void addHardwareDevice(in int deviceId);
void removeHardwareDevice(in int deviceId);
+
+ // For freezing video playback
+ void setVideoFrozen(in IBinder sessionToken, boolean isFrozen, int userId);
}
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index e37ee6e342e5..a93f18db3dca 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -83,4 +83,7 @@ oneway interface ITvInputSession {
// For TV messages
void notifyTvMessage(int type, in Bundle data);
void setTvMessageEnabled(int type, boolean enabled);
+
+ // For freezing video
+ void setVideoFrozen(boolean isFrozen);
}
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index ae3ee6535c71..921104dc70c8 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -81,6 +81,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
private static final int DO_NOTIFY_TV_MESSAGE = 32;
private static final int DO_STOP_PLAYBACK = 33;
private static final int DO_START_PLAYBACK = 34;
+ private static final int DO_SET_VIDEO_FROZEN = 35;
private final boolean mIsRecordingSession;
private final HandlerCaller mCaller;
@@ -296,6 +297,10 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
mTvInputSessionImpl.startPlayback();
break;
}
+ case DO_SET_VIDEO_FROZEN: {
+ mTvInputSessionImpl.setVideoFrozen((Boolean) msg.obj);
+ break;
+ }
default: {
Log.w(TAG, "Unhandled message code: " + msg.what);
break;
@@ -483,6 +488,11 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand
}
@Override
+ public void setVideoFrozen(boolean isFrozen) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_VIDEO_FROZEN, isFrozen));
+ }
+
+ @Override
public void notifyTvMessage(int type, Bundle data) {
mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_NOTIFY_TV_MESSAGE, type, data));
}
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index db0195056074..7f8f1a3f22ef 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -16,6 +16,7 @@
package android.media.tv;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -29,6 +30,7 @@ import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
+import android.media.tv.flags.Flags;
import android.net.Uri;
import android.os.Bundle;
import android.os.IBinder;
@@ -2540,9 +2542,9 @@ public final class TvContract {
* <p>This is used to indicate the broadcast visibility type defined in the underlying
* broadcast standard or country/operator profile, if applicable. For example,
* {@code visible_service_flag} and {@code numeric_selection_flag} of
- * {@code service_attribute_descriptor} in D-Book, {@code visible_service_flag} and
- * {@code selectable_service_flag} of {@code ciplus_service_descriptor} in CI Plus 1.3
- * specification.
+ * {@code service_attribute_descriptor} in D-Book, the specification for UK-based TV
+ * products, {@code visible_service_flag} and {@code selectable_service_flag} of
+ * {@code ciplus_service_descriptor} in the CI Plus 1.3 specification.
*
* <p>The value should match one of the following:
* {@link #BROADCAST_VISIBILITY_TYPE_VISIBLE},
@@ -2553,8 +2555,8 @@ public final class TvContract {
* by default.
*
* <p>Type: INTEGER
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES)
public static final String COLUMN_BROADCAST_VISIBILITY_TYPE = "broadcast_visibility_type";
/** @hide */
@@ -2571,8 +2573,8 @@ public final class TvContract {
* visible from users and selectable by users via normal service navigation mechanisms.
*
* @see #COLUMN_BROADCAST_VISIBILITY_TYPE
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES)
public static final int BROADCAST_VISIBILITY_TYPE_VISIBLE = 0;
/**
@@ -2581,18 +2583,18 @@ public final class TvContract {
* the logical channel number.
*
* @see #COLUMN_BROADCAST_VISIBILITY_TYPE
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES)
public static final int BROADCAST_VISIBILITY_TYPE_NUMERIC_SELECTABLE_ONLY = 1;
/**
* The broadcast visibility type for invisible services. Use this type when the service
- * is invisible from users and unselectable by users via any of normal service navigation
- * mechanisms.
+ * is invisible from users and not able to be selected by users via any of the normal
+ * service navigation mechanisms.
*
* @see #COLUMN_BROADCAST_VISIBILITY_TYPE
- * @hide
*/
+ @FlaggedApi(Flags.FLAG_BROADCAST_VISIBILITY_TYPES)
public static final int BROADCAST_VISIBILITY_TYPE_INVISIBLE = 2;
private Channels() {}
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index c685a5adb08b..51b2542688a6 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -3334,6 +3334,18 @@ public final class TvInputManager {
}
}
+ void setVideoFrozen(boolean isFrozen) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.setVideoFrozen(mToken, isFrozen, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Sends TV messages to the service for testing purposes
*/
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 55fa51755177..76d8e50f94be 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1558,6 +1558,17 @@ public abstract class TvInputService extends Service {
}
/**
+ * Called when a request to freeze the video is received from the TV app. The audio should
+ * continue playback while the video is frozen.
+ *
+ * <p> This should freeze the video to the last frame when the state is set to {@code true}.
+ * @param isFrozen whether or not the video should be frozen.
+ * @hide
+ */
+ public void onSetVideoFrozen(boolean isFrozen) {
+ }
+
+ /**
* Called when the application requests to play a given recorded TV program.
*
* @param recordedProgramUri The URI of a recorded TV program.
@@ -2034,6 +2045,13 @@ public abstract class TvInputService extends Service {
}
/**
+ * Calls {@link #onSetVideoFrozen(boolean)}.
+ */
+ void setVideoFrozen(boolean isFrozen) {
+ onSetVideoFrozen(isFrozen);
+ }
+
+ /**
* Calls {@link #onTimeShiftPlay(Uri)}.
*/
void timeShiftPlay(Uri recordedProgramUri) {
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index 78d7d7631145..2ebb19a7767e 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -55,6 +55,27 @@ public final class TvTrackInfo implements Parcelable {
*/
public static final int TYPE_SUBTITLE = 2;
+ /**
+ * The component tag identifies a component carried by a MPEG-2 TS.
+ *
+ * This corresponds to the component_tag in the component descriptor in the
+ * Elementary Stream loop of the stream in the Program Map Table
+ * (PMT) [EN 300 468], or undefined if the component is not carried in an
+ * MPEG-2 TS.
+ *
+ * @hide
+ */
+ public static final String EXTRA_BUNDLE_KEY_COMPONENT_TAG = "component_tag";
+
+ /**
+ * The MPEG Program ID (PID) of the component in the MPEG2-TS in
+ * which it is carried, or undefined if the component is not carried in an
+ * MPEG-2 TS.
+ *
+ * @hide
+ */
+ public static final String EXTRA_BUNDLE_KEY_PID = "pid";
+
private final int mType;
private final String mId;
private final String mLanguage;
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 233f96675543..cb45661a01ac 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -675,6 +675,23 @@ public class TvView extends ViewGroup {
}
/**
+ * Sets whether or not the video is frozen. While the video is frozen, audio playback will
+ * continue.
+ *
+ * <p> This should be invoked after a {@link TvInteractiveAppService.Session#requestCommand} is
+ * received with the command to freeze the video.
+ *
+ * <p> This will freeze the video to the last frame when the state is set to {@code true}.
+ * @param isFrozen whether or not the video is frozen.
+ * @hide
+ */
+ public void setVideoFrozen(boolean isFrozen) {
+ if (mSession != null) {
+ mSession.setVideoFrozen(isFrozen);
+ }
+ }
+
+ /**
* Sends TV messages to the session for testing purposes
*
* @hide
diff --git a/media/java/android/media/tv/ad/ITvAdClient.aidl b/media/java/android/media/tv/ad/ITvAdClient.aidl
new file mode 100644
index 000000000000..34d96b374a35
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdClient.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+import android.view.InputChannel;
+
+/**
+ * Interface a client of the ITvAdManager implements, to identify itself and receive
+ * information about changes to the state of each TV AD service.
+ * @hide
+ */
+oneway interface ITvAdClient {
+ void onSessionCreated(in String serviceId, IBinder token, in InputChannel channel, int seq);
+ void onSessionReleased(int seq);
+ void onLayoutSurface(int left, int top, int right, int bottom, int seq);
+} \ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdManager.aidl b/media/java/android/media/tv/ad/ITvAdManager.aidl
index 92cc923dc9ab..a747e4995869 100644
--- a/media/java/android/media/tv/ad/ITvAdManager.aidl
+++ b/media/java/android/media/tv/ad/ITvAdManager.aidl
@@ -16,10 +16,25 @@
package android.media.tv.ad;
+import android.media.tv.ad.ITvAdClient;
+import android.media.tv.ad.ITvAdManagerCallback;
+import android.media.tv.ad.TvAdServiceInfo;
+import android.view.Surface;
+
/**
* Interface to the TV AD service.
* @hide
*/
interface ITvAdManager {
+ List<TvAdServiceInfo> getTvAdServiceList(int userId);
+ void createSession(
+ in ITvAdClient client, in String serviceId, in String type, int seq, int userId);
+ void releaseSession(in IBinder sessionToken, int userId);
void startAdService(in IBinder sessionToken, int userId);
+ void setSurface(in IBinder sessionToken, in Surface surface, int userId);
+ void dispatchSurfaceChanged(in IBinder sessionToken, int format, int width, int height,
+ int userId);
+
+ void registerCallback(in ITvAdManagerCallback callback, int userId);
+ void unregisterCallback(in ITvAdManagerCallback callback, int userId);
}
diff --git a/media/java/android/media/tv/ad/ITvAdManagerCallback.aidl b/media/java/android/media/tv/ad/ITvAdManagerCallback.aidl
new file mode 100644
index 000000000000..f55f67e616db
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdManagerCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+/**
+ * Interface to receive callbacks from ITvAdManager regardless of sessions.
+ * @hide
+ */
+oneway interface ITvAdManagerCallback {
+ void onAdServiceAdded(in String serviceId);
+ void onAdServiceRemoved(in String serviceId);
+ void onAdServiceUpdated(in String serviceId);
+} \ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdService.aidl b/media/java/android/media/tv/ad/ITvAdService.aidl
new file mode 100644
index 000000000000..3bb04097abca
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdService.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+import android.media.tv.ad.ITvAdServiceCallback;
+import android.media.tv.ad.ITvAdSessionCallback;
+import android.os.Bundle;
+import android.view.InputChannel;
+
+/**
+ * Top-level interface to a TV AD component (implemented in a Service). It's used for
+ * TvAdManagerService to communicate with TvAdService.
+ * @hide
+ */
+oneway interface ITvAdService {
+ void registerCallback(in ITvAdServiceCallback callback);
+ void unregisterCallback(in ITvAdServiceCallback callback);
+ void createSession(in InputChannel channel, in ITvAdSessionCallback callback,
+ in String serviceId, in String type);
+ void sendAppLinkCommand(in Bundle command);
+} \ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdServiceCallback.aidl b/media/java/android/media/tv/ad/ITvAdServiceCallback.aidl
new file mode 100644
index 000000000000..a087181e097d
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdServiceCallback.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+/**
+ * Helper interface for ITvAdService to allow the TvAdService to notify the TvAdManagerService.
+ * @hide
+ */
+oneway interface ITvAdServiceCallback {
+} \ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdSession.aidl b/media/java/android/media/tv/ad/ITvAdSession.aidl
index b834f1b9fb92..751257ce4d4e 100644
--- a/media/java/android/media/tv/ad/ITvAdSession.aidl
+++ b/media/java/android/media/tv/ad/ITvAdSession.aidl
@@ -16,10 +16,15 @@
package android.media.tv.ad;
+import android.view.Surface;
+
/**
- * Sub-interface of ITvAdService which is created per session and has its own context.
+ * Sub-interface of ITvAdService.aidl which is created per session and has its own context.
* @hide
*/
oneway interface ITvAdSession {
+ void release();
void startAdService();
+ void setSurface(in Surface surface);
+ void dispatchSurfaceChanged(int format, int width, int height);
}
diff --git a/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl b/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
new file mode 100644
index 000000000000..f21ef198ab01
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdSessionCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+import android.media.tv.ad.ITvAdSession;
+
+/**
+ * Helper interface for ITvAdSession to allow TvAdService to notify the system service when there is
+ * a related event.
+ * @hide
+ */
+oneway interface ITvAdSessionCallback {
+ void onSessionCreated(in ITvAdSession session);
+ void onLayoutSurface(int left, int top, int right, int bottom);
+} \ No newline at end of file
diff --git a/media/java/android/media/tv/ad/ITvAdSessionWrapper.java b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
new file mode 100644
index 000000000000..4df2783f6511
--- /dev/null
+++ b/media/java/android/media/tv/ad/ITvAdSessionWrapper.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.ad;
+
+import android.content.Context;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.Surface;
+
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+/**
+ * Implements the internal ITvAdSession interface.
+ * @hide
+ */
+public class ITvAdSessionWrapper
+ extends ITvAdSession.Stub implements HandlerCaller.Callback {
+
+ private static final String TAG = "ITvAdSessionWrapper";
+
+ private static final int EXECUTE_MESSAGE_TIMEOUT_SHORT_MILLIS = 1000;
+ private static final int EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS = 5 * 1000;
+ private static final int DO_RELEASE = 1;
+ private static final int DO_SET_SURFACE = 2;
+ private static final int DO_DISPATCH_SURFACE_CHANGED = 3;
+
+ private final HandlerCaller mCaller;
+ private TvAdService.Session mSessionImpl;
+ private InputChannel mChannel;
+ private TvAdEventReceiver mReceiver;
+
+ public ITvAdSessionWrapper(
+ Context context, TvAdService.Session mSessionImpl, InputChannel channel) {
+ this.mSessionImpl = mSessionImpl;
+ mCaller = new HandlerCaller(context, null, this, true /* asyncHandler */);
+ mChannel = channel;
+ if (channel != null) {
+ mReceiver = new TvAdEventReceiver(channel, context.getMainLooper());
+ }
+ }
+
+ @Override
+ public void release() {
+ mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_RELEASE));
+ }
+
+
+ @Override
+ public void executeMessage(Message msg) {
+ if (mSessionImpl == null) {
+ return;
+ }
+
+ long startTime = System.nanoTime();
+ switch (msg.what) {
+ case DO_RELEASE: {
+ mSessionImpl.release();
+ mSessionImpl = null;
+ if (mReceiver != null) {
+ mReceiver.dispose();
+ mReceiver = null;
+ }
+ if (mChannel != null) {
+ mChannel.dispose();
+ mChannel = null;
+ }
+ break;
+ }
+ case DO_SET_SURFACE: {
+ mSessionImpl.setSurface((Surface) msg.obj);
+ break;
+ }
+ case DO_DISPATCH_SURFACE_CHANGED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ mSessionImpl.dispatchSurfaceChanged(
+ (Integer) args.argi1, (Integer) args.argi2, (Integer) args.argi3);
+ args.recycle();
+ break;
+ }
+ default: {
+ Log.w(TAG, "Unhandled message code: " + msg.what);
+ break;
+ }
+ }
+ long durationMs = (System.nanoTime() - startTime) / (1000 * 1000);
+ if (durationMs > EXECUTE_MESSAGE_TIMEOUT_SHORT_MILLIS) {
+ Log.w(TAG, "Handling message (" + msg.what + ") took too long time (duration="
+ + durationMs + "ms)");
+ if (durationMs > EXECUTE_MESSAGE_TIMEOUT_LONG_MILLIS) {
+ // TODO: handle timeout
+ }
+ }
+
+ }
+
+ @Override
+ public void startAdService() throws RemoteException {
+
+ }
+
+ @Override
+ public void setSurface(Surface surface) {
+ mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_SURFACE, surface));
+ }
+
+ @Override
+ public void dispatchSurfaceChanged(int format, int width, int height) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageIIII(DO_DISPATCH_SURFACE_CHANGED, format, width, height, 0));
+ }
+
+ private final class TvAdEventReceiver extends InputEventReceiver {
+ TvAdEventReceiver(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event) {
+ if (mSessionImpl == null) {
+ // The session has been finished.
+ finishInputEvent(event, false);
+ return;
+ }
+
+ int handled = mSessionImpl.dispatchInputEvent(event, this);
+ if (handled != TvAdManager.Session.DISPATCH_IN_PROGRESS) {
+ finishInputEvent(
+ event, handled == TvAdManager.Session.DISPATCH_HANDLED);
+ }
+ }
+ }
+}
diff --git a/media/java/android/media/tv/ad/TvAdManager.java b/media/java/android/media/tv/ad/TvAdManager.java
index 2b52c4b107b6..9c7505197dec 100644
--- a/media/java/android/media/tv/ad/TvAdManager.java
+++ b/media/java/android/media/tv/ad/TvAdManager.java
@@ -17,12 +17,30 @@
package android.media.tv.ad;
import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.Context;
+import android.media.tv.TvInputManager;
import android.media.tv.flags.Flags;
+import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
import android.os.RemoteException;
import android.util.Log;
+import android.util.Pools;
+import android.util.SparseArray;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventSender;
+import android.view.Surface;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
/**
* Central system API to the overall client-side TV AD architecture, which arbitrates interaction
@@ -37,10 +55,163 @@ public class TvAdManager {
private final ITvAdManager mService;
private final int mUserId;
+ // A mapping from the sequence number of a session to its SessionCallbackRecord.
+ private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap =
+ new SparseArray<>();
+
+ // @GuardedBy("mLock")
+ private final List<TvAdServiceCallbackRecord> mCallbackRecords = new ArrayList<>();
+
+ // A sequence number for the next session to be created. Should be protected by a lock
+ // {@code mSessionCallbackRecordMap}.
+ private int mNextSeq;
+
+ private final Object mLock = new Object();
+ private final ITvAdClient mClient;
+
/** @hide */
public TvAdManager(ITvAdManager service, int userId) {
mService = service;
mUserId = userId;
+ mClient = new ITvAdClient.Stub() {
+ @Override
+ public void onSessionCreated(String serviceId, IBinder token, InputChannel channel,
+ int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for " + token);
+ return;
+ }
+ Session session = null;
+ if (token != null) {
+ session = new Session(token, channel, mService, mUserId, seq,
+ mSessionCallbackRecordMap);
+ } else {
+ mSessionCallbackRecordMap.delete(seq);
+ }
+ record.postSessionCreated(session);
+ }
+ }
+
+ @Override
+ public void onSessionReleased(int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ mSessionCallbackRecordMap.delete(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq:" + seq);
+ return;
+ }
+ record.mSession.releaseInternal();
+ record.postSessionReleased();
+ }
+ }
+
+ @Override
+ public void onLayoutSurface(int left, int top, int right, int bottom, int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postLayoutSurface(left, top, right, bottom);
+ }
+ }
+
+ };
+
+ ITvAdManagerCallback managerCallback =
+ new ITvAdManagerCallback.Stub() {
+ @Override
+ public void onAdServiceAdded(String serviceId) {
+ synchronized (mLock) {
+ for (TvAdServiceCallbackRecord record : mCallbackRecords) {
+ record.postAdServiceAdded(serviceId);
+ }
+ }
+ }
+
+ @Override
+ public void onAdServiceRemoved(String serviceId) {
+ synchronized (mLock) {
+ for (TvAdServiceCallbackRecord record : mCallbackRecords) {
+ record.postAdServiceRemoved(serviceId);
+ }
+ }
+ }
+
+ @Override
+ public void onAdServiceUpdated(String serviceId) {
+ synchronized (mLock) {
+ for (TvAdServiceCallbackRecord record : mCallbackRecords) {
+ record.postAdServiceUpdated(serviceId);
+ }
+ }
+ }
+ };
+ try {
+ if (mService != null) {
+ mService.registerCallback(managerCallback, mUserId);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns the complete list of TV AD service on the system.
+ *
+ * @return List of {@link TvAdServiceInfo} for each TV AD service that describes its meta
+ * information.
+ * @hide
+ */
+ @NonNull
+ public List<TvAdServiceInfo> getTvAdServiceList() {
+ try {
+ return mService.getTvAdServiceList(mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Creates a {@link Session} for a given TV AD service.
+ *
+ * <p>The number of sessions that can be created at the same time is limited by the capability
+ * of the given AD service.
+ *
+ * @param serviceId The ID of the AD service.
+ * @param callback A callback used to receive the created session.
+ * @param handler A {@link Handler} that the session creation will be delivered to.
+ * @hide
+ */
+ public void createSession(
+ @NonNull String serviceId,
+ @NonNull String type,
+ @NonNull final TvAdManager.SessionCallback callback,
+ @NonNull Handler handler) {
+ createSessionInternal(serviceId, type, callback, handler);
+ }
+
+ private void createSessionInternal(String serviceId, String type,
+ TvAdManager.SessionCallback callback, Handler handler) {
+ Preconditions.checkNotNull(serviceId);
+ Preconditions.checkNotNull(type);
+ Preconditions.checkNotNull(callback);
+ Preconditions.checkNotNull(handler);
+ TvAdManager.SessionCallbackRecord
+ record = new TvAdManager.SessionCallbackRecord(callback, handler);
+ synchronized (mSessionCallbackRecordMap) {
+ int seq = mNextSeq++;
+ mSessionCallbackRecordMap.put(seq, record);
+ try {
+ mService.createSession(mClient, serviceId, type, seq, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
/**
@@ -48,14 +219,121 @@ public class TvAdManager {
* @hide
*/
public static final class Session {
- private final IBinder mToken;
+ static final int DISPATCH_IN_PROGRESS = -1;
+ static final int DISPATCH_NOT_HANDLED = 0;
+ static final int DISPATCH_HANDLED = 1;
+
+ private static final long INPUT_SESSION_NOT_RESPONDING_TIMEOUT = 2500;
private final ITvAdManager mService;
private final int mUserId;
+ private final int mSeq;
+ private final SparseArray<SessionCallbackRecord> mSessionCallbackRecordMap;
- private Session(IBinder token, ITvAdManager service, int userId) {
+ // For scheduling input event handling on the main thread. This also serves as a lock to
+ // protect pending input events and the input channel.
+ private final InputEventHandler mHandler = new InputEventHandler(Looper.getMainLooper());
+
+ private TvInputManager.Session mInputSession;
+ private final Pools.Pool<PendingEvent> mPendingEventPool = new Pools.SimplePool<>(20);
+ private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
+ private TvInputEventSender mSender;
+ private InputChannel mInputChannel;
+ private IBinder mToken;
+
+ private Session(IBinder token, InputChannel channel, ITvAdManager service, int userId,
+ int seq, SparseArray<SessionCallbackRecord> sessionCallbackRecordMap) {
mToken = token;
+ mInputChannel = channel;
mService = service;
mUserId = userId;
+ mSeq = seq;
+ mSessionCallbackRecordMap = sessionCallbackRecordMap;
+ }
+
+ /**
+ * Releases this session.
+ */
+ public void release() {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.releaseSession(mToken, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+
+ releaseInternal();
+ }
+
+ /**
+ * Sets the {@link android.view.Surface} for this session.
+ *
+ * @param surface A {@link android.view.Surface} used to render AD.
+ */
+ public void setSurface(Surface surface) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ // surface can be null.
+ try {
+ mService.setSurface(mToken, surface, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Notifies of any structural changes (format or size) of the surface passed in
+ * {@link #setSurface}.
+ *
+ * @param format The new PixelFormat of the surface.
+ * @param width The new width of the surface.
+ * @param height The new height of the surface.
+ */
+ public void dispatchSurfaceChanged(int format, int width, int height) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.dispatchSurfaceChanged(mToken, format, width, height, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private void flushPendingEventsLocked() {
+ mHandler.removeMessages(InputEventHandler.MSG_FLUSH_INPUT_EVENT);
+
+ final int count = mPendingEvents.size();
+ for (int i = 0; i < count; i++) {
+ int seq = mPendingEvents.keyAt(i);
+ Message msg = mHandler.obtainMessage(
+ InputEventHandler.MSG_FLUSH_INPUT_EVENT, seq, 0);
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
+ }
+ }
+
+ private void releaseInternal() {
+ mToken = null;
+ synchronized (mHandler) {
+ if (mInputChannel != null) {
+ if (mSender != null) {
+ flushPendingEventsLocked();
+ mSender.dispose();
+ mSender = null;
+ }
+ mInputChannel.dispose();
+ mInputChannel = null;
+ }
+ }
+ synchronized (mSessionCallbackRecordMap) {
+ mSessionCallbackRecordMap.delete(mSeq);
+ }
}
void startAdService() {
@@ -69,5 +347,324 @@ public class TvAdManager {
throw e.rethrowFromSystemServer();
}
}
+
+ private final class InputEventHandler extends Handler {
+ public static final int MSG_SEND_INPUT_EVENT = 1;
+ public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
+ public static final int MSG_FLUSH_INPUT_EVENT = 3;
+
+ InputEventHandler(Looper looper) {
+ super(looper, null, true);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_SEND_INPUT_EVENT: {
+ sendInputEventAndReportResultOnMainLooper((PendingEvent) msg.obj);
+ return;
+ }
+ case MSG_TIMEOUT_INPUT_EVENT: {
+ finishedInputEvent(msg.arg1, false, true);
+ return;
+ }
+ case MSG_FLUSH_INPUT_EVENT: {
+ finishedInputEvent(msg.arg1, false, false);
+ return;
+ }
+ }
+ }
+ }
+
+ // Assumes the event has already been removed from the queue.
+ void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
+ p.mHandled = handled;
+ if (p.mEventHandler.getLooper().isCurrentThread()) {
+ // Already running on the callback handler thread so we can send the callback
+ // immediately.
+ p.run();
+ } else {
+ // Post the event to the callback handler thread.
+ // In this case, the callback will be responsible for recycling the event.
+ Message msg = Message.obtain(p.mEventHandler, p);
+ msg.setAsynchronous(true);
+ msg.sendToTarget();
+ }
+ }
+
+ // Must be called on the main looper
+ private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
+ synchronized (mHandler) {
+ int result = sendInputEventOnMainLooperLocked(p);
+ if (result == DISPATCH_IN_PROGRESS) {
+ return;
+ }
+ }
+
+ invokeFinishedInputEventCallback(p, false);
+ }
+
+ private int sendInputEventOnMainLooperLocked(PendingEvent p) {
+ if (mInputChannel != null) {
+ if (mSender == null) {
+ mSender = new TvInputEventSender(mInputChannel, mHandler.getLooper());
+ }
+
+ final InputEvent event = p.mEvent;
+ final int seq = event.getSequenceNumber();
+ if (mSender.sendInputEvent(seq, event)) {
+ mPendingEvents.put(seq, p);
+ Message msg = mHandler.obtainMessage(
+ InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
+ msg.setAsynchronous(true);
+ mHandler.sendMessageDelayed(msg, INPUT_SESSION_NOT_RESPONDING_TIMEOUT);
+ return DISPATCH_IN_PROGRESS;
+ }
+
+ Log.w(TAG, "Unable to send input event to session: " + mToken + " dropping:"
+ + event);
+ }
+ return DISPATCH_NOT_HANDLED;
+ }
+
+ void finishedInputEvent(int seq, boolean handled, boolean timeout) {
+ final PendingEvent p;
+ synchronized (mHandler) {
+ int index = mPendingEvents.indexOfKey(seq);
+ if (index < 0) {
+ return; // spurious, event already finished or timed out
+ }
+
+ p = mPendingEvents.valueAt(index);
+ mPendingEvents.removeAt(index);
+
+ if (timeout) {
+ Log.w(TAG, "Timeout waiting for session to handle input event after "
+ + INPUT_SESSION_NOT_RESPONDING_TIMEOUT + " ms: " + mToken);
+ } else {
+ mHandler.removeMessages(InputEventHandler.MSG_TIMEOUT_INPUT_EVENT, p);
+ }
+ }
+
+ invokeFinishedInputEventCallback(p, handled);
+ }
+
+ private void recyclePendingEventLocked(PendingEvent p) {
+ p.recycle();
+ mPendingEventPool.release(p);
+ }
+
+ /**
+ * Callback that is invoked when an input event that was dispatched to this session has been
+ * finished.
+ *
+ * @hide
+ */
+ public interface FinishedInputEventCallback {
+ /**
+ * Called when the dispatched input event is finished.
+ *
+ * @param token A token passed to {@link #dispatchInputEvent}.
+ * @param handled {@code true} if the dispatched input event was handled properly.
+ * {@code false} otherwise.
+ */
+ void onFinishedInputEvent(Object token, boolean handled);
+ }
+
+ private final class TvInputEventSender extends InputEventSender {
+ TvInputEventSender(InputChannel inputChannel, Looper looper) {
+ super(inputChannel, looper);
+ }
+
+ @Override
+ public void onInputEventFinished(int seq, boolean handled) {
+ finishedInputEvent(seq, handled, false);
+ }
+ }
+
+ private final class PendingEvent implements Runnable {
+ public InputEvent mEvent;
+ public Object mEventToken;
+ public Session.FinishedInputEventCallback mCallback;
+ public Handler mEventHandler;
+ public boolean mHandled;
+
+ public void recycle() {
+ mEvent = null;
+ mEventToken = null;
+ mCallback = null;
+ mEventHandler = null;
+ mHandled = false;
+ }
+
+ @Override
+ public void run() {
+ mCallback.onFinishedInputEvent(mEventToken, mHandled);
+
+ synchronized (mEventHandler) {
+ recyclePendingEventLocked(this);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Interface used to receive the created session.
+ * @hide
+ */
+ public abstract static class SessionCallback {
+ /**
+ * This is called after {@link TvAdManager#createSession} has been processed.
+ *
+ * @param session A {@link TvAdManager.Session} instance created. This can be
+ * {@code null} if the creation request failed.
+ */
+ public void onSessionCreated(@Nullable Session session) {
+ }
+
+ /**
+ * This is called when {@link TvAdManager.Session} is released.
+ * This typically happens when the process hosting the session has crashed or been killed.
+ *
+ * @param session the {@link TvAdManager.Session} instance released.
+ */
+ public void onSessionReleased(@NonNull Session session) {
+ }
+
+ /**
+ * This is called when {@link TvAdService.Session#layoutSurface} is called to
+ * change the layout of surface.
+ *
+ * @param session A {@link TvAdManager.Session} associated with this callback.
+ * @param left Left position.
+ * @param top Top position.
+ * @param right Right position.
+ * @param bottom Bottom position.
+ */
+ public void onLayoutSurface(Session session, int left, int top, int right, int bottom) {
+ }
+
+ }
+
+ /**
+ * Callback used to monitor status of the TV AD service.
+ * @hide
+ */
+ public abstract static class TvAdServiceCallback {
+ /**
+ * This is called when a TV AD service is added to the system.
+ *
+ * <p>Normally it happens when the user installs a new TV AD service package that implements
+ * {@link TvAdService} interface.
+ *
+ * @param serviceId The ID of the TV AD service.
+ */
+ public void onAdServiceAdded(@NonNull String serviceId) {
+ }
+
+ /**
+ * This is called when a TV AD service is removed from the system.
+ *
+ * <p>Normally it happens when the user uninstalls the previously installed TV AD service
+ * package.
+ *
+ * @param serviceId The ID of the TV AD service.
+ */
+ public void onAdServiceRemoved(@NonNull String serviceId) {
+ }
+
+ /**
+ * This is called when a TV AD service is updated on the system.
+ *
+ * <p>Normally it happens when a previously installed TV AD service package is re-installed
+ * or a newer version of the package exists becomes available/unavailable.
+ *
+ * @param serviceId The ID of the TV AD service.
+ */
+ public void onAdServiceUpdated(@NonNull String serviceId) {
+ }
+
+ }
+
+ private static final class SessionCallbackRecord {
+ private final SessionCallback mSessionCallback;
+ private final Handler mHandler;
+ private Session mSession;
+
+ SessionCallbackRecord(SessionCallback sessionCallback, Handler handler) {
+ mSessionCallback = sessionCallback;
+ mHandler = handler;
+ }
+
+ void postSessionCreated(final Session session) {
+ mSession = session;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onSessionCreated(session);
+ }
+ });
+ }
+
+ void postSessionReleased() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onSessionReleased(mSession);
+ }
+ });
+ }
+
+ void postLayoutSurface(final int left, final int top, final int right,
+ final int bottom) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onLayoutSurface(mSession, left, top, right, bottom);
+ }
+ });
+ }
+ }
+
+ private static final class TvAdServiceCallbackRecord {
+ private final TvAdServiceCallback mCallback;
+ private final Executor mExecutor;
+
+ TvAdServiceCallbackRecord(TvAdServiceCallback callback, Executor executor) {
+ mCallback = callback;
+ mExecutor = executor;
+ }
+
+ public TvAdServiceCallback getCallback() {
+ return mCallback;
+ }
+
+ public void postAdServiceAdded(final String serviceId) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onAdServiceAdded(serviceId);
+ }
+ });
+ }
+
+ public void postAdServiceRemoved(final String serviceId) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onAdServiceRemoved(serviceId);
+ }
+ });
+ }
+
+ public void postAdServiceUpdated(final String serviceId) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onAdServiceUpdated(serviceId);
+ }
+ });
+ }
}
}
diff --git a/media/java/android/media/tv/ad/TvAdService.java b/media/java/android/media/tv/ad/TvAdService.java
index 6897a78647c2..699570397e34 100644
--- a/media/java/android/media/tv/ad/TvAdService.java
+++ b/media/java/android/media/tv/ad/TvAdService.java
@@ -16,8 +16,37 @@
package android.media.tv.ad;
+import android.annotation.CallSuper;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.internal.os.SomeArgs;
+
+import java.util.ArrayList;
+import java.util.List;
/**
* The TvAdService class represents a TV client-side advertisement service.
@@ -36,9 +65,123 @@ public abstract class TvAdService extends Service {
public static final String SERVICE_META_DATA = "android.media.tv.ad.service";
/**
+ * This is the interface name that a service implementing a TV AD service should
+ * say that it supports -- that is, this is the action it uses for its intent filter. To be
+ * supported, the service must also require the
+ * android.Manifest.permission#BIND_TV_AD_SERVICE permission so that other
+ * applications cannot abuse it.
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE = "android.media.tv.ad.TvAdService";
+
+ private final Handler mServiceHandler = new ServiceHandler();
+ private final RemoteCallbackList<ITvAdServiceCallback> mCallbacks = new RemoteCallbackList<>();
+
+ @Override
+ @Nullable
+ public final IBinder onBind(@NonNull Intent intent) {
+ ITvAdService.Stub tvAdServiceBinder = new ITvAdService.Stub() {
+ @Override
+ public void registerCallback(ITvAdServiceCallback cb) {
+ if (cb != null) {
+ mCallbacks.register(cb);
+ }
+ }
+
+ @Override
+ public void unregisterCallback(ITvAdServiceCallback cb) {
+ if (cb != null) {
+ mCallbacks.unregister(cb);
+ }
+ }
+
+ @Override
+ public void createSession(InputChannel channel, ITvAdSessionCallback cb,
+ String serviceId, String type) {
+ if (cb == null) {
+ return;
+ }
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = channel;
+ args.arg2 = cb;
+ args.arg3 = serviceId;
+ args.arg4 = type;
+ mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args)
+ .sendToTarget();
+ }
+
+ @Override
+ public void sendAppLinkCommand(Bundle command) {
+ onAppLinkCommand(command);
+ }
+ };
+ return tvAdServiceBinder;
+ }
+
+ /**
+ * Called when app link command is received.
+ */
+ public void onAppLinkCommand(@NonNull Bundle command) {
+ }
+
+
+ /**
+ * Returns a concrete implementation of {@link Session}.
+ *
+ * <p>May return {@code null} if this TV AD service fails to create a session for some
+ * reason.
+ *
+ * @param serviceId The ID of the TV AD associated with the session.
+ * @param type The type of the TV AD associated with the session.
+ */
+ @Nullable
+ public abstract Session onCreateSession(@NonNull String serviceId, @NonNull String type);
+
+ /**
* Base class for derived classes to implement to provide a TV AD session.
*/
public abstract static class Session implements KeyEvent.Callback {
+ private final KeyEvent.DispatcherState mDispatcherState = new KeyEvent.DispatcherState();
+
+ private final Object mLock = new Object();
+ // @GuardedBy("mLock")
+ private ITvAdSessionCallback mSessionCallback;
+ // @GuardedBy("mLock")
+ private final List<Runnable> mPendingActions = new ArrayList<>();
+ private final Context mContext;
+ final Handler mHandler;
+ private final WindowManager mWindowManager;
+ private Surface mSurface;
+
+
+ /**
+ * Creates a new Session.
+ *
+ * @param context The context of the application
+ */
+ public Session(@NonNull Context context) {
+ mContext = context;
+ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mHandler = new Handler(context.getMainLooper());
+ }
+
+ /**
+ * Releases TvAdService session.
+ */
+ public abstract void onRelease();
+
+ void release() {
+ onRelease();
+ if (mSurface != null) {
+ mSurface.release();
+ mSurface = null;
+ }
+ synchronized (mLock) {
+ mSessionCallback = null;
+ mPendingActions.clear();
+ }
+ }
+
/**
* Starts TvAdService session.
*/
@@ -48,21 +191,264 @@ public abstract class TvAdService extends Service {
void startAdService() {
onStartAdService();
}
- }
- /**
- * Implements the internal ITvAdService interface.
- */
- public static class ITvAdSessionWrapper extends ITvAdSession.Stub {
- private final Session mSessionImpl;
+ @Override
+ public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
+ return false;
+ }
+
+ @Override
+ public boolean onKeyLongPress(int keyCode, @NonNull KeyEvent event) {
+ return false;
+ }
- public ITvAdSessionWrapper(Session mSessionImpl) {
- this.mSessionImpl = mSessionImpl;
+ @Override
+ public boolean onKeyMultiple(int keyCode, int count, @NonNull KeyEvent event) {
+ return false;
}
@Override
- public void startAdService() {
- mSessionImpl.startAdService();
+ public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
+ return false;
}
+
+ /**
+ * Implement this method to handle touch screen motion events on the current session.
+ *
+ * @param event The motion event being received.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ * @see View#onTouchEvent
+ */
+ public boolean onTouchEvent(@NonNull MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Implement this method to handle trackball events on the current session.
+ *
+ * @param event The motion event being received.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ * @see View#onTrackballEvent
+ */
+ public boolean onTrackballEvent(@NonNull MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Implement this method to handle generic motion events on the current session.
+ *
+ * @param event The motion event being received.
+ * @return If you handled the event, return {@code true}. If you want to allow the event to
+ * be handled by the next receiver, return {@code false}.
+ * @see View#onGenericMotionEvent
+ */
+ public boolean onGenericMotionEvent(@NonNull MotionEvent event) {
+ return false;
+ }
+
+ /**
+ * Assigns a size and position to the surface passed in {@link #onSetSurface}. The position
+ * is relative to the overlay view that sits on top of this surface.
+ *
+ * @param left Left position in pixels, relative to the overlay view.
+ * @param top Top position in pixels, relative to the overlay view.
+ * @param right Right position in pixels, relative to the overlay view.
+ * @param bottom Bottom position in pixels, relative to the overlay view.
+ */
+ @CallSuper
+ public void layoutSurface(final int left, final int top, final int right,
+ final int bottom) {
+ if (left > right || top > bottom) {
+ throw new IllegalArgumentException("Invalid parameter");
+ }
+ executeOrPostRunnableOnMainThread(new Runnable() {
+ @MainThread
+ @Override
+ public void run() {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "layoutSurface (l=" + left + ", t=" + top
+ + ", r=" + right + ", b=" + bottom + ",)");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onLayoutSurface(left, top, right, bottom);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in layoutSurface", e);
+ }
+ }
+ });
+ }
+
+ /**
+ * Called when the application sets the surface.
+ *
+ * <p>The TV AD service should render AD UI onto the given surface. When called with
+ * {@code null}, the AD service should immediately free any references to the currently set
+ * surface and stop using it.
+ *
+ * @param surface The surface to be used for AD UI rendering. Can be {@code null}.
+ * @return {@code true} if the surface was set successfully, {@code false} otherwise.
+ */
+ public abstract boolean onSetSurface(@Nullable Surface surface);
+
+ /**
+ * Called after any structural changes (format or size) have been made to the surface passed
+ * in {@link #onSetSurface}. This method is always called at least once, after
+ * {@link #onSetSurface} is called with non-null surface.
+ *
+ * @param format The new {@link PixelFormat} of the surface.
+ * @param width The new width of the surface.
+ * @param height The new height of the surface.
+ */
+ public void onSurfaceChanged(@PixelFormat.Format int format, int width, int height) {
+ }
+
+ /**
+ * Takes care of dispatching incoming input events and tells whether the event was handled.
+ */
+ int dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
+ if (DEBUG) Log.d(TAG, "dispatchInputEvent(" + event + ")");
+ if (event instanceof KeyEvent) {
+ KeyEvent keyEvent = (KeyEvent) event;
+ if (keyEvent.dispatch(this, mDispatcherState, this)) {
+ return TvAdManager.Session.DISPATCH_HANDLED;
+ }
+
+ // TODO: special handlings of navigation keys and media keys
+ } else if (event instanceof MotionEvent) {
+ MotionEvent motionEvent = (MotionEvent) event;
+ final int source = motionEvent.getSource();
+ if (motionEvent.isTouchEvent()) {
+ if (onTouchEvent(motionEvent)) {
+ return TvAdManager.Session.DISPATCH_HANDLED;
+ }
+ } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
+ if (onTrackballEvent(motionEvent)) {
+ return TvAdManager.Session.DISPATCH_HANDLED;
+ }
+ } else {
+ if (onGenericMotionEvent(motionEvent)) {
+ return TvAdManager.Session.DISPATCH_HANDLED;
+ }
+ }
+ }
+ // TODO: handle overlay view
+ return TvAdManager.Session.DISPATCH_NOT_HANDLED;
+ }
+
+
+ private void initialize(ITvAdSessionCallback callback) {
+ synchronized (mLock) {
+ mSessionCallback = callback;
+ for (Runnable runnable : mPendingActions) {
+ runnable.run();
+ }
+ mPendingActions.clear();
+ }
+ }
+
+ /**
+ * Calls {@link #onSetSurface}.
+ */
+ void setSurface(Surface surface) {
+ onSetSurface(surface);
+ if (mSurface != null) {
+ mSurface.release();
+ }
+ mSurface = surface;
+ // TODO: Handle failure.
+ }
+
+ /**
+ * Calls {@link #onSurfaceChanged}.
+ */
+ void dispatchSurfaceChanged(int format, int width, int height) {
+ if (DEBUG) {
+ Log.d(TAG, "dispatchSurfaceChanged(format=" + format + ", width=" + width
+ + ", height=" + height + ")");
+ }
+ onSurfaceChanged(format, width, height);
+ }
+
+ private void executeOrPostRunnableOnMainThread(Runnable action) {
+ synchronized (mLock) {
+ if (mSessionCallback == null) {
+ // The session is not initialized yet.
+ mPendingActions.add(action);
+ } else {
+ if (mHandler.getLooper().isCurrentThread()) {
+ action.run();
+ } else {
+ // Posts the runnable if this is not called from the main thread
+ mHandler.post(action);
+ }
+ }
+ }
+ }
+ }
+
+
+ @SuppressLint("HandlerLeak")
+ private final class ServiceHandler extends Handler {
+ private static final int DO_CREATE_SESSION = 1;
+ private static final int DO_NOTIFY_SESSION_CREATED = 2;
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case DO_CREATE_SESSION: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ InputChannel channel = (InputChannel) args.arg1;
+ ITvAdSessionCallback cb = (ITvAdSessionCallback) args.arg2;
+ String serviceId = (String) args.arg3;
+ String type = (String) args.arg4;
+ args.recycle();
+ TvAdService.Session sessionImpl = onCreateSession(serviceId, type);
+ if (sessionImpl == null) {
+ try {
+ // Failed to create a session.
+ cb.onSessionCreated(null);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in onSessionCreated", e);
+ }
+ return;
+ }
+ ITvAdSession stub =
+ new ITvAdSessionWrapper(TvAdService.this, sessionImpl, channel);
+
+ SomeArgs someArgs = SomeArgs.obtain();
+ someArgs.arg1 = sessionImpl;
+ someArgs.arg2 = stub;
+ someArgs.arg3 = cb;
+ mServiceHandler.obtainMessage(
+ DO_NOTIFY_SESSION_CREATED, someArgs).sendToTarget();
+ return;
+ }
+ case DO_NOTIFY_SESSION_CREATED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ Session sessionImpl = (Session) args.arg1;
+ ITvAdSession stub = (ITvAdSession) args.arg2;
+ ITvAdSessionCallback cb = (ITvAdSessionCallback) args.arg3;
+ try {
+ cb.onSessionCreated(stub);
+ } catch (RemoteException e) {
+ Log.e(TAG, "error in onSessionCreated", e);
+ }
+ if (sessionImpl != null) {
+ sessionImpl.initialize(cb);
+ }
+ args.recycle();
+ return;
+ }
+ default: {
+ Log.w(TAG, "Unhandled message code: " + msg.what);
+ return;
+ }
+ }
+ }
+
}
}
diff --git a/media/java/android/media/tv/ad/TvAdServiceInfo.java b/media/java/android/media/tv/ad/TvAdServiceInfo.java
index ed04f1f9058c..45dc89d838d8 100644
--- a/media/java/android/media/tv/ad/TvAdServiceInfo.java
+++ b/media/java/android/media/tv/ad/TvAdServiceInfo.java
@@ -24,6 +24,7 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.os.Parcel;
import android.os.Parcelable;
@@ -63,8 +64,7 @@ public final class TvAdServiceInfo implements Parcelable {
if (context == null) {
throw new IllegalArgumentException("context cannot be null.");
}
- // TODO: use a constant
- Intent intent = new Intent("android.media.tv.ad.TvAdService").setComponent(component);
+ Intent intent = new Intent(TvAdService.SERVICE_INTERFACE).setComponent(component);
ResolveInfo resolveInfo = context.getPackageManager().resolveService(
intent, PackageManager.GET_SERVICES | PackageManager.GET_META_DATA);
if (resolveInfo == null) {
@@ -80,6 +80,7 @@ public final class TvAdServiceInfo implements Parcelable {
mService = resolveInfo;
mId = id;
+ mTypes.addAll(types);
}
private TvAdServiceInfo(ResolveInfo service, String id, List<String> types) {
@@ -147,9 +148,8 @@ public final class TvAdServiceInfo implements Parcelable {
ResolveInfo resolveInfo, Context context, List<String> types) {
ServiceInfo serviceInfo = resolveInfo.serviceInfo;
PackageManager pm = context.getPackageManager();
- // TODO: use constant for the metadata
try (XmlResourceParser parser =
- serviceInfo.loadXmlMetaData(pm, "android.media.tv.ad.service")) {
+ serviceInfo.loadXmlMetaData(pm, TvAdService.SERVICE_META_DATA)) {
if (parser == null) {
throw new IllegalStateException(
"No " + "android.media.tv.ad.service"
@@ -171,7 +171,15 @@ public final class TvAdServiceInfo implements Parcelable {
+ XML_START_TAG_NAME + " tag for " + serviceInfo.name);
}
- // TODO: parse attributes
+ TypedArray sa = resources.obtainAttributes(attrs,
+ com.android.internal.R.styleable.TvAdService);
+ CharSequence[] textArr = sa.getTextArray(
+ com.android.internal.R.styleable.TvAdService_adServiceTypes);
+ for (CharSequence cs : textArr) {
+ types.add(cs.toString().toLowerCase());
+ }
+
+ sa.recycle();
} catch (IOException | XmlPullParserException e) {
throw new IllegalStateException(
"Failed reading meta-data for " + serviceInfo.packageName, e);
diff --git a/media/java/android/media/tv/ad/TvAdView.java b/media/java/android/media/tv/ad/TvAdView.java
index 1a3771a9f24c..5e67fe9f697b 100644
--- a/media/java/android/media/tv/ad/TvAdView.java
+++ b/media/java/android/media/tv/ad/TvAdView.java
@@ -16,8 +16,20 @@
package android.media.tv.ad;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.util.AttributeSet;
import android.util.Log;
+import android.util.Xml;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
import android.view.ViewGroup;
/**
@@ -28,18 +40,166 @@ public class TvAdView extends ViewGroup {
private static final String TAG = "TvAdView";
private static final boolean DEBUG = false;
- // TODO: create session
+ private final TvAdManager mTvAdManager;
+
+ private final Handler mHandler = new Handler();
private TvAdManager.Session mSession;
+ private MySessionCallback mSessionCallback;
+
+ private final AttributeSet mAttrs;
+ private final int mDefStyleAttr;
+ private final XmlResourceParser mParser;
+
+ private SurfaceView mSurfaceView;
+ private Surface mSurface;
+
+ private boolean mSurfaceChanged;
+ private int mSurfaceFormat;
+ private int mSurfaceWidth;
+ private int mSurfaceHeight;
+
+ private boolean mUseRequestedSurfaceLayout;
+ private int mSurfaceViewLeft;
+ private int mSurfaceViewRight;
+ private int mSurfaceViewTop;
+ private int mSurfaceViewBottom;
+
+
+
+ private final SurfaceHolder.Callback mSurfaceHolderCallback = new SurfaceHolder.Callback() {
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ if (DEBUG) {
+ Log.d(TAG, "surfaceChanged(holder=" + holder + ", format=" + format
+ + ", width=" + width + ", height=" + height + ")");
+ }
+ mSurfaceFormat = format;
+ mSurfaceWidth = width;
+ mSurfaceHeight = height;
+ mSurfaceChanged = true;
+ dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
+ }
+
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ mSurface = holder.getSurface();
+ setSessionSurface(mSurface);
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ mSurface = null;
+ mSurfaceChanged = false;
+ setSessionSurface(null);
+ }
+ };
+
+
+ public TvAdView(@NonNull Context context) {
+ this(context, null, 0);
+ }
+
+ public TvAdView(@NonNull Context context, @Nullable AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TvAdView(@NonNull Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ int sourceResId = Resources.getAttributeSetSourceResId(attrs);
+ if (sourceResId != Resources.ID_NULL) {
+ Log.d(TAG, "Build local AttributeSet");
+ mParser = context.getResources().getXml(sourceResId);
+ mAttrs = Xml.asAttributeSet(mParser);
+ } else {
+ Log.d(TAG, "Use passed in AttributeSet");
+ mParser = null;
+ mAttrs = attrs;
+ }
+ mDefStyleAttr = defStyleAttr;
+ resetSurfaceView();
+ mTvAdManager = (TvAdManager) getContext().getSystemService(Context.TV_AD_SERVICE);
+ }
+
+ @Override
+ public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (DEBUG) {
+ Log.d(TAG, "onLayout (left=" + left + ", top=" + top + ", right=" + right
+ + ", bottom=" + bottom + ",)");
+ }
+ if (mUseRequestedSurfaceLayout) {
+ mSurfaceView.layout(mSurfaceViewLeft, mSurfaceViewTop, mSurfaceViewRight,
+ mSurfaceViewBottom);
+ } else {
+ mSurfaceView.layout(0, 0, right - left, bottom - top);
+ }
+ }
- public TvAdView(Context context) {
- super(context, /* attrs = */null, /* defStyleAttr = */0);
+ @Override
+ public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ mSurfaceView.measure(widthMeasureSpec, heightMeasureSpec);
+ int width = mSurfaceView.getMeasuredWidth();
+ int height = mSurfaceView.getMeasuredHeight();
+ int childState = mSurfaceView.getMeasuredState();
+ setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, childState),
+ resolveSizeAndState(height, heightMeasureSpec,
+ childState << MEASURED_HEIGHT_STATE_SHIFT));
}
@Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ public void onVisibilityChanged(@NonNull View changedView, int visibility) {
+ super.onVisibilityChanged(changedView, visibility);
+ mSurfaceView.setVisibility(visibility);
+ }
+
+ private void resetSurfaceView() {
+ if (mSurfaceView != null) {
+ mSurfaceView.getHolder().removeCallback(mSurfaceHolderCallback);
+ removeView(mSurfaceView);
+ }
+ mSurface = null;
+ mSurfaceView = new SurfaceView(getContext(), mAttrs, mDefStyleAttr) {
+ @Override
+ protected void updateSurface() {
+ super.updateSurface();
+ }};
+ // The surface view's content should be treated as secure all the time.
+ mSurfaceView.setSecure(true);
+ mSurfaceView.getHolder().addCallback(mSurfaceHolderCallback);
+ mSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
+
+ mSurfaceView.setZOrderOnTop(false);
+ mSurfaceView.setZOrderMediaOverlay(true);
+
+ addView(mSurfaceView);
+ }
+
+ private void setSessionSurface(Surface surface) {
+ if (mSession == null) {
+ return;
+ }
+ mSession.setSurface(surface);
+ }
+
+ private void dispatchSurfaceChanged(int format, int width, int height) {
+ if (mSession == null) {
+ return;
+ }
+ //mSession.dispatchSurfaceChanged(format, width, height);
+ }
+
+ /**
+ * Prepares the AD service of corresponding {@link TvAdService}.
+ *
+ * @param serviceId the AD service ID, which can be found in TvAdServiceInfo#getId().
+ */
+ public void prepareAdService(@NonNull String serviceId, @NonNull String type) {
if (DEBUG) {
- Log.d(TAG,
- "onLayout (left=" + l + ", top=" + t + ", right=" + r + ", bottom=" + b + ",)");
+ Log.d(TAG, "prepareAdService");
+ }
+ mSessionCallback = new TvAdView.MySessionCallback(serviceId);
+ if (mTvAdManager != null) {
+ mTvAdManager.createSession(serviceId, type, mSessionCallback, mHandler);
}
}
@@ -54,4 +214,75 @@ public class TvAdView extends ViewGroup {
mSession.startAdService();
}
}
+
+ private class MySessionCallback extends TvAdManager.SessionCallback {
+ final String mServiceId;
+
+ MySessionCallback(String serviceId) {
+ mServiceId = serviceId;
+ }
+
+ @Override
+ public void onSessionCreated(TvAdManager.Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onSessionCreated()");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onSessionCreated - session already created");
+ // This callback is obsolete.
+ if (session != null) {
+ session.release();
+ }
+ return;
+ }
+ mSession = session;
+ if (session != null) {
+ // mSurface may not be ready yet as soon as starting an application.
+ // In the case, we don't send Session.setSurface(null) unnecessarily.
+ // setSessionSurface will be called in surfaceCreated.
+ if (mSurface != null) {
+ setSessionSurface(mSurface);
+ if (mSurfaceChanged) {
+ dispatchSurfaceChanged(mSurfaceFormat, mSurfaceWidth, mSurfaceHeight);
+ }
+ }
+ } else {
+ // Failed to create
+ // Todo: forward error to Tv App
+ mSessionCallback = null;
+ }
+ }
+
+ @Override
+ public void onSessionReleased(TvAdManager.Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onSessionReleased()");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onSessionReleased - session not created");
+ return;
+ }
+ mSessionCallback = null;
+ mSession = null;
+ }
+
+ @Override
+ public void onLayoutSurface(
+ TvAdManager.Session session, int left, int top, int right, int bottom) {
+ if (DEBUG) {
+ Log.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top + ", right="
+ + right + ", bottom=" + bottom + ",)");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onLayoutSurface - session not created");
+ return;
+ }
+ mSurfaceViewLeft = left;
+ mSurfaceViewTop = top;
+ mSurfaceViewRight = right;
+ mSurfaceViewBottom = bottom;
+ mUseRequestedSurfaceLayout = true;
+ requestLayout();
+ }
+ }
}
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index 77391841c6fe..e3dba03d6093 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -48,6 +48,7 @@ oneway interface ITvInteractiveAppClient {
void onRequestCurrentChannelLcn(int seq);
void onRequestStreamVolume(int seq);
void onRequestTrackInfoList(int seq);
+ void onRequestSelectedTrackInfo(int seq);
void onRequestCurrentTvInputId(int seq);
void onRequestTimeShiftMode(int seq);
void onRequestAvailableSpeeds(int seq);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index 41cbe4ae02d0..4316d053a275 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -102,6 +102,8 @@ interface ITvInteractiveAppManager {
int UserId);
void notifyAdResponse(in IBinder sessionToken, in AdResponse response, int UserId);
void notifyAdBufferConsumed(in IBinder sessionToken, in AdBuffer buffer, int userId);
+ void sendSelectedTrackInfo(in IBinder sessionToken, in List<TvTrackInfo> tracks,
+ int userId);
void createMediaView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 052bc3d5adce..ba7cf13a7a1d 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -78,6 +78,7 @@ oneway interface ITvInteractiveAppSession {
void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
void notifyAdResponse(in AdResponse response);
void notifyAdBufferConsumed(in AdBuffer buffer);
+ void sendSelectedTrackInfo(in List<TvTrackInfo> tracks);
void createMediaView(in IBinder windowToken, in Rect frame);
void relayoutMediaView(in Rect frame);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index 9e43e79144fd..416b8f12d5ea 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -50,6 +50,7 @@ oneway interface ITvInteractiveAppSessionCallback {
void onRequestCurrentTvInputId();
void onRequestTimeShiftMode();
void onRequestAvailableSpeeds();
+ void onRequestSelectedTrackInfo();
void onRequestStartRecording(in String requestId, in Uri programUri);
void onRequestStopRecording(in String recordingId);
void onRequestScheduleRecording(in String requestId, in String inputId, in Uri channelUri,
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index 253ade809ece..518b08a93f95 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -102,6 +102,7 @@ public class ITvInteractiveAppSessionWrapper
private static final int DO_NOTIFY_RECORDING_SCHEDULED = 45;
private static final int DO_SEND_TIME_SHIFT_MODE = 46;
private static final int DO_SEND_AVAILABLE_SPEEDS = 47;
+ private static final int DO_SEND_SELECTED_TRACK_INFO = 48;
private final HandlerCaller mCaller;
private Session mSessionImpl;
@@ -247,6 +248,10 @@ public class ITvInteractiveAppSessionWrapper
args.recycle();
break;
}
+ case DO_SEND_SELECTED_TRACK_INFO: {
+ mSessionImpl.sendSelectedTrackInfo((List<TvTrackInfo>) msg.obj);
+ break;
+ }
case DO_NOTIFY_VIDEO_AVAILABLE: {
mSessionImpl.notifyVideoAvailable();
break;
@@ -526,6 +531,12 @@ public class ITvInteractiveAppSessionWrapper
}
@Override
+ public void sendSelectedTrackInfo(List<TvTrackInfo> tracks) {
+ mCaller.executeOrSendMessage(
+ mCaller.obtainMessageO(DO_SEND_SELECTED_TRACK_INFO, tracks));
+ }
+
+ @Override
public void notifyTracksChanged(List<TvTrackInfo> tracks) {
mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_TRACKS_CHANGED, tracks));
}
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 7cce84a1ee16..bf4379f470d8 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -33,7 +33,6 @@ import android.media.tv.TvContentRating;
import android.media.tv.TvInputManager;
import android.media.tv.TvRecordingInfo;
import android.media.tv.TvTrackInfo;
-import android.media.tv.interactive.TvInteractiveAppService.Session;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -506,6 +505,18 @@ public final class TvInteractiveAppManager {
}
@Override
+ public void onRequestSelectedTrackInfo(int seq) {
+ synchronized (mSessionCallbackRecordMap) {
+ SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+ if (record == null) {
+ Log.e(TAG, "Callback not found for seq " + seq);
+ return;
+ }
+ record.postRequestSelectedTrackInfo();
+ }
+ }
+
+ @Override
public void onRequestCurrentTvInputId(int seq) {
synchronized (mSessionCallbackRecordMap) {
SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
@@ -1209,6 +1220,18 @@ public final class TvInteractiveAppManager {
}
}
+ void sendSelectedTrackInfo(@NonNull List<TvTrackInfo> tracks) {
+ if (mToken == null) {
+ Log.w(TAG, "The session has been already released");
+ return;
+ }
+ try {
+ mService.sendSelectedTrackInfo(mToken, tracks, mUserId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
void sendCurrentTvInputId(@Nullable String inputId) {
if (mToken == null) {
Log.w(TAG, "The session has been already released");
@@ -2108,6 +2131,15 @@ public final class TvInteractiveAppManager {
});
}
+ void postRequestSelectedTrackInfo() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSessionCallback.onRequestSelectedTrackInfo(mSession);
+ }
+ });
+ }
+
void postRequestCurrentTvInputId() {
mHandler.post(new Runnable() {
@Override
@@ -2378,6 +2410,15 @@ public final class TvInteractiveAppManager {
}
/**
+ * This is called when {@link TvInteractiveAppService.Session#requestSelectedTrackInfo()} is
+ * called.
+ *
+ * @param session A {@link TvInteractiveAppManager.Session} associated with this callback.
+ */
+ public void onRequestSelectedTrackInfo(Session session) {
+ }
+
+ /**
* This is called when {@link TvInteractiveAppService.Session#requestCurrentTvInputId} is
* called.
*
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 241940486a14..79364034ac2a 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -112,7 +112,8 @@ public abstract class TvInteractiveAppService extends Service {
PLAYBACK_COMMAND_TYPE_TUNE_PREV,
PLAYBACK_COMMAND_TYPE_STOP,
PLAYBACK_COMMAND_TYPE_SET_STREAM_VOLUME,
- PLAYBACK_COMMAND_TYPE_SELECT_TRACK
+ PLAYBACK_COMMAND_TYPE_SELECT_TRACK,
+ PLAYBACK_COMMAND_TYPE_FREEZE
})
public @interface PlaybackCommandType {}
@@ -142,8 +143,11 @@ public abstract class TvInteractiveAppService extends Service {
* Playback command type: select the given track.
*/
public static final String PLAYBACK_COMMAND_TYPE_SELECT_TRACK = "select_track";
-
-
+ /**
+ * Playback command type: freeze the video playback on the current frame.
+ * @hide
+ */
+ public static final String PLAYBACK_COMMAND_TYPE_FREEZE = "freeze";
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -932,6 +936,16 @@ public abstract class TvInteractiveAppService extends Service {
@NonNull Bundle data) {
}
+ /**
+ * Called when the TV App sends the selected track info as a response to
+ * requestSelectedTrackInfo.
+ *
+ * @param tracks
+ * @hide
+ */
+ public void onSelectedTrackInfo(List<TvTrackInfo> tracks) {
+ }
+
@Override
public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
return false;
@@ -1338,6 +1352,30 @@ public abstract class TvInteractiveAppService extends Service {
}
/**
+ * Requests the currently selected {@link TvTrackInfo} from the TV App.
+ *
+ * <p> Normally, track info cannot be synchronized until the channel has
+ * been changed. This is used when the session of the TIAS is newly
+ * created and the normal synchronization has not happened yet.
+ * @hide
+ */
+ @CallSuper
+ public void requestSelectedTrackInfo() {
+ executeOrPostRunnableOnMainThread(() -> {
+ try {
+ if (DEBUG) {
+ Log.d(TAG, "requestSelectedTrackInfo");
+ }
+ if (mSessionCallback != null) {
+ mSessionCallback.onRequestSelectedTrackInfo();
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "error in requestSelectedTrackInfo", e);
+ }
+ });
+ }
+
+ /**
* Requests starting of recording
*
* <p> This is used to request the active {@link android.media.tv.TvRecordingClient} to
@@ -1781,6 +1819,13 @@ public abstract class TvInteractiveAppService extends Service {
onTvMessage(type, data);
}
+ void sendSelectedTrackInfo(List<TvTrackInfo> tracks) {
+ if (DEBUG) {
+ Log.d(TAG, "notifySelectedTrackInfo (tracks= " + tracks + ")");
+ }
+ onSelectedTrackInfo(tracks);
+ }
+
/**
* Calls {@link #onAdBufferConsumed}.
*/
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
index cbaf5e482faa..40a12e4db4cc 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java
@@ -582,6 +582,20 @@ public class TvInteractiveAppView extends ViewGroup {
}
/**
+ * Sends the currently selected track info to the TV Interactive App.
+ *
+ * @hide
+ */
+ public void sendSelectedTrackInfo(@Nullable List<TvTrackInfo> tracks) {
+ if (DEBUG) {
+ Log.d(TAG, "sendSelectedTrackInfo");
+ }
+ if (mSession != null) {
+ mSession.sendSelectedTrackInfo(tracks);
+ }
+ }
+
+ /**
* Sends current TV input ID to related TV interactive app.
*
* @param inputId The current TV input ID whose channel is tuned. {@code null} if no channel is
@@ -1197,6 +1211,16 @@ public class TvInteractiveAppView extends ViewGroup {
}
/**
+ * This is called when {@link TvInteractiveAppService.Session#requestSelectedTrackInfo()} is
+ * called.
+ *
+ * @param iAppServiceId The ID of the TV interactive app service bound to this view.
+ * @hide
+ */
+ public void onRequestSelectedTrackInfo(@NonNull String iAppServiceId) {
+ }
+
+ /**
* This is called when {@link TvInteractiveAppService.Session#requestCurrentTvInputId()} is
* called.
*
@@ -1714,6 +1738,28 @@ public class TvInteractiveAppView extends ViewGroup {
}
@Override
+ public void onRequestSelectedTrackInfo(Session session) {
+ if (DEBUG) {
+ Log.d(TAG, "onRequestSelectedTrackInfo");
+ }
+ if (this != mSessionCallback) {
+ Log.w(TAG, "onRequestSelectedTrackInfo - session not created");
+ return;
+ }
+ synchronized (mCallbackLock) {
+ if (mCallbackExecutor != null) {
+ mCallbackExecutor.execute(() -> {
+ synchronized (mCallbackLock) {
+ if (mCallback != null) {
+ mCallback.onRequestSelectedTrackInfo(mIAppServiceId);
+ }
+ }
+ });
+ }
+ }
+ }
+
+ @Override
public void onRequestCurrentTvInputId(Session session) {
if (DEBUG) {
Log.d(TAG, "onRequestCurrentTvInputId");
diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java
index f9eaabd800b1..0b9429debb62 100644
--- a/media/java/android/media/tv/tuner/Lnb.java
+++ b/media/java/android/media/tv/tuner/Lnb.java
@@ -264,6 +264,25 @@ public class Lnb implements AutoCloseable {
}
}
+ /* package */ void closeInternal() {
+ synchronized (mLock) {
+ if (mIsClosed) {
+ return;
+ }
+ int res = nativeClose();
+ if (res != Tuner.RESULT_SUCCESS) {
+ TunerUtils.throwExceptionForResult(res, "Failed to close LNB");
+ } else {
+ mIsClosed = true;
+ if (mOwner != null) {
+ mOwner.releaseLnb();
+ mOwner = null;
+ }
+ mCallbackMap.clear();
+ }
+ }
+ }
+
/**
* Sets the LNB's power voltage.
*
@@ -330,22 +349,7 @@ public class Lnb implements AutoCloseable {
public void close() {
acquireTRMSLock("close()");
try {
- synchronized (mLock) {
- if (mIsClosed) {
- return;
- }
- int res = nativeClose();
- if (res != Tuner.RESULT_SUCCESS) {
- TunerUtils.throwExceptionForResult(res, "Failed to close LNB");
- } else {
- mIsClosed = true;
- if (mOwner != null) {
- mOwner.releaseLnb();
- mOwner = null;
- }
- mCallbackMap.clear();
- }
- }
+ closeInternal();
} finally {
releaseTRMSLock();
}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index 09f09b94ac0d..f28c2c17167f 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -30,6 +30,7 @@ import android.content.pm.PackageManager;
import android.hardware.tv.tuner.Constant;
import android.hardware.tv.tuner.Constant64Bit;
import android.hardware.tv.tuner.FrontendScanType;
+import android.media.MediaCodec;
import android.media.tv.TvInputService;
import android.media.tv.tuner.dvr.DvrPlayback;
import android.media.tv.tuner.dvr.DvrRecorder;
@@ -272,8 +273,12 @@ public class Tuner implements AutoCloseable {
try {
System.loadLibrary("media_tv_tuner");
nativeInit();
+ // Load and initialize MediaCodec to avoid flaky cts test result.
+ Class.forName(MediaCodec.class.getName());
} catch (UnsatisfiedLinkError e) {
Log.d(TAG, "tuner JNI library not found!");
+ } catch (ClassNotFoundException e) {
+ Log.e(TAG, "MediaCodec class not found!", e);
}
}
@@ -914,7 +919,7 @@ public class Tuner implements AutoCloseable {
if (DEBUG) {
Log.d(TAG, "calling mLnb.close() : " + mClientId);
}
- mLnb.close();
+ mLnb.closeInternal();
} else {
if (DEBUG) {
Log.d(TAG, "NOT calling mLnb.close() : " + mClientId);
@@ -2348,6 +2353,7 @@ public class Tuner implements AutoCloseable {
@Nullable
public Lnb openLnbByName(@NonNull String name, @CallbackExecutor @NonNull Executor executor,
@NonNull LnbCallback cb) {
+ acquireTRMSLock("openLnbByName");
mLnbLock.lock();
try {
Objects.requireNonNull(name, "LNB name must not be null");
@@ -2356,7 +2362,7 @@ public class Tuner implements AutoCloseable {
Lnb newLnb = nativeOpenLnbByName(name);
if (newLnb != null) {
if (mLnb != null) {
- mLnb.close();
+ mLnb.closeInternal();
mLnbHandle = null;
}
mLnb = newLnb;
@@ -2367,6 +2373,7 @@ public class Tuner implements AutoCloseable {
}
return mLnb;
} finally {
+ releaseTRMSLock();
mLnbLock.unlock();
}
}
@@ -2784,8 +2791,8 @@ public class Tuner implements AutoCloseable {
}
}
+ // Must be called while TRMS lock is being held
/* package */ void releaseLnb() {
- acquireTRMSLock("releaseLnb()");
mLnbLock.lock();
try {
if (mLnbHandle != null) {
@@ -2802,7 +2809,6 @@ public class Tuner implements AutoCloseable {
}
mLnb = null;
} finally {
- releaseTRMSLock();
mLnbLock.unlock();
}
}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 3fcb8713672f..00b0e57c09ea 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -967,7 +967,7 @@ void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
ScopedLocalRef<jobject> filter(env);
{
android::Mutex::Autolock autoLock(mLock);
- if (env->IsSameObject(filter.get(), nullptr)) {
+ if (env->IsSameObject(mFilterObj, nullptr)) {
ALOGE("FilterClientCallbackImpl::onFilterStatus:"
"Filter object has been freed. Ignoring callback.");
return;
diff --git a/nfc/Android.bp b/nfc/Android.bp
index 87da299464e4..7136866d536f 100644
--- a/nfc/Android.bp
+++ b/nfc/Android.bp
@@ -68,7 +68,7 @@ java_sdk_library {
],
jarjar_rules: ":nfc-jarjar-rules",
lint: {
- strict_updatability_linting: true,
+ baseline_filename: "lint-baseline.xml",
},
apex_available: [
"//apex_available:platform",
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index 24c145f890c4..7573474f65b4 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -204,6 +204,7 @@ package android.nfc.cardemulation {
method public boolean isDefaultServiceForAid(android.content.ComponentName, String);
method public boolean isDefaultServiceForCategory(android.content.ComponentName, String);
method public boolean registerAidsForService(android.content.ComponentName, String, java.util.List<java.lang.String>);
+ method @FlaggedApi("android.nfc.nfc_read_polling_loop") public boolean registerPollingLoopFilterForService(@NonNull android.content.ComponentName, @NonNull String);
method public boolean removeAidsForService(android.content.ComponentName, String);
method @NonNull @RequiresPermission(android.Manifest.permission.NFC) public boolean setOffHostForService(@NonNull android.content.ComponentName, @NonNull String);
method public boolean setPreferredService(android.app.Activity, android.content.ComponentName);
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index 286cf2890eea..bec62c5b1b82 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -97,4 +97,7 @@ interface INfcAdapter
WlcLDeviceInfo getWlcLDeviceInfo();
void updateDiscoveryTechnology(IBinder b, int pollFlags, int listenFlags);
+
+ void notifyPollingLoop(in Bundle frame);
+ void notifyHceDeactivated();
}
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index f4b46046bc3e..791bd8c9e6f4 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -32,6 +32,7 @@ interface INfcCardEmulation
boolean setDefaultForNextTap(int userHandle, in ComponentName service);
boolean setServiceObserveModeDefault(int userId, in android.content.ComponentName service, boolean enable);
boolean registerAidGroupForService(int userHandle, in ComponentName service, in AidGroup aidGroup);
+ boolean registerPollingLoopFilterForService(int userHandle, in ComponentName service, in String pollingLoopFilter);
boolean setOffHostForService(int userHandle, in ComponentName service, in String offHostSecureElement);
boolean unsetOffHostForService(int userHandle, in ComponentName service);
AidGroup getAidGroupForService(int userHandle, in ComponentName service, String category);
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 8219d2f873de..68c16e69f3af 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -26,6 +26,7 @@ import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.annotation.UserIdInt;
import android.app.Activity;
import android.app.PendingIntent;
@@ -2752,6 +2753,64 @@ public final class NfcAdapter {
}
}
+ /**
+ * Notifies the system of a new polling loop.
+ *
+ * @param frame is the new frame.
+ *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public void notifyPollingLoop(@NonNull Bundle frame) {
+ try {
+ if (sService == null) {
+ attemptDeadServiceRecovery(null);
+ }
+ sService.notifyPollingLoop(frame);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return;
+ }
+ try {
+ sService.notifyPollingLoop(frame);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ }
+ }
+
+ /**
+ * Notifies the system of a an HCE session being deactivated.
+ * *
+ * @hide
+ */
+ @TestApi
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public void notifyHceDeactivated() {
+ try {
+ if (sService == null) {
+ attemptDeadServiceRecovery(null);
+ }
+ sService.notifyHceDeactivated();
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ // Try one more time
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ return;
+ }
+ try {
+ sService.notifyHceDeactivated();
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to recover NFC Service.");
+ }
+ }
+ }
+
/**
* Sets NFC charging feature.
* <p>This API is for the Settings application.
@@ -2767,6 +2826,7 @@ public final class NfcAdapter {
}
try {
return sService.enableWlc(enable);
+
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
// Try one more time
diff --git a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
index 41dee3ab035c..426c5aac487b 100644
--- a/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
+++ b/nfc/java/android/nfc/cardemulation/ApduServiceInfo.java
@@ -52,6 +52,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;
@@ -102,6 +103,8 @@ public final class ApduServiceInfo implements Parcelable {
*/
private final HashMap<String, AidGroup> mDynamicAidGroups;
+ private final ArrayList<String> mPollingLoopFilters;
+
/**
* Whether this service should only be started when the device is unlocked.
*/
@@ -169,6 +172,7 @@ public final class ApduServiceInfo implements Parcelable {
this.mDescription = description;
this.mStaticAidGroups = new HashMap<String, AidGroup>();
this.mDynamicAidGroups = new HashMap<String, AidGroup>();
+ this.mPollingLoopFilters = new ArrayList<String>();
this.mOffHostName = offHost;
this.mStaticOffHostName = staticOffHost;
this.mOnHost = onHost;
@@ -282,6 +286,7 @@ public final class ApduServiceInfo implements Parcelable {
mStaticAidGroups = new HashMap<String, AidGroup>();
mDynamicAidGroups = new HashMap<String, AidGroup>();
+ mPollingLoopFilters = new ArrayList<String>();
mOnHost = onHost;
final int depth = parser.getDepth();
@@ -364,6 +369,15 @@ public final class ApduServiceInfo implements Parcelable {
Log.e(TAG, "Ignoring invalid or duplicate aid: " + aid);
}
a.recycle();
+ } else if (eventType == XmlPullParser.START_TAG
+ && "polling-loop-filter".equals(tagName) && currentGroup == null) {
+ final TypedArray a = res.obtainAttributes(attrs,
+ com.android.internal.R.styleable.PollingLoopFilter);
+ String plf =
+ a.getString(com.android.internal.R.styleable.PollingLoopFilter_name)
+ .toUpperCase(Locale.ROOT);
+ mPollingLoopFilters.add(plf);
+ a.recycle();
}
}
} catch (NameNotFoundException e) {
@@ -420,6 +434,16 @@ public final class ApduServiceInfo implements Parcelable {
}
/**
+ * Returns the current polling loop filters for this service.
+ * @return List of polling loop filters.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ @NonNull
+ public List<String> getPollingLoopFilters() {
+ return mPollingLoopFilters;
+ }
+
+ /**
* Returns a consolidated list of AIDs with prefixes from the AID groups
* registered by this service. Note that if a service has both
* a static (manifest-based) AID group for a category and a dynamic
@@ -596,6 +620,26 @@ public final class ApduServiceInfo implements Parcelable {
}
/**
+ * Add a Polling Loop Filter. Custom NFC polling frames that match this filter will be
+ * delivered to {@link HostApduService#processPollingFrames(List)}.
+ * @param pollingLoopFilter this polling loop filter to add.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public void addPollingLoopFilter(@NonNull String pollingLoopFilter) {
+ mPollingLoopFilters.add(pollingLoopFilter.toUpperCase(Locale.ROOT));
+ }
+
+ /**
+ * Remove a Polling Loop Filter. Custom NFC polling frames that match this filter will no
+ * longer be delivered to {@link HostApduService#processPollingFrames(List)}.
+ * @param pollingLoopFilter this polling loop filter to add.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public void removePollingLoopFilter(@NonNull String pollingLoopFilter) {
+ mPollingLoopFilters.remove(pollingLoopFilter.toUpperCase(Locale.ROOT));
+ }
+
+ /**
* Sets the off host Secure Element.
* @param offHost Secure Element to set. Only accept strings with prefix SIM or prefix eSE.
* Ref: GSMA TS.26 - NFC Handset Requirements
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index 81eab71fe080..0943392a68ad 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -57,6 +57,8 @@ import java.util.regex.Pattern;
*/
public final class CardEmulation {
private static final Pattern AID_PATTERN = Pattern.compile("[0-9A-Fa-f]{10,32}\\*?\\#?");
+ private static final Pattern PLF_PATTERN = Pattern.compile("[0-9A-Fa-f]{1,32}");
+
static final String TAG = "CardEmulation";
/**
@@ -353,6 +355,34 @@ public final class CardEmulation {
}
/**
+ * Register a polling loop filter for a HostApduService.
+ * @param service The HostApduService to register the filter for.
+ * @param pollingLoopFilter The filter to register.
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public boolean registerPollingLoopFilterForService(@NonNull ComponentName service,
+ @NonNull String pollingLoopFilter) {
+ try {
+ return sService.registerPollingLoopFilterForService(mContext.getUser().getIdentifier(),
+ service, pollingLoopFilter);
+ } catch (RemoteException e) {
+ // Try one more time
+ recoverService();
+ if (sService == null) {
+ Log.e(TAG, "Failed to recover CardEmulationService.");
+ return false;
+ }
+ try {
+ return sService.registerPollingLoopFilterForService(
+ mContext.getUser().getIdentifier(), service, pollingLoopFilter);
+ } catch (RemoteException ee) {
+ Log.e(TAG, "Failed to reach CardEmulationService.");
+ return false;
+ }
+ }
+ }
+
+ /**
* Registers a list of AIDs for a specific category for the
* specified service.
*
@@ -933,6 +963,24 @@ public final class CardEmulation {
}
/**
+ * Tests the validity of the polling loop filter.
+ * @param pollingLoopFilter The polling loop filter to test.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_NFC_READ_POLLING_LOOP)
+ public static boolean isValidPollingLoopFilter(@NonNull String pollingLoopFilter) {
+ // Verify hex characters
+ if (!PLF_PATTERN.matcher(pollingLoopFilter).matches()) {
+ Log.e(TAG, "Polling Loop Filter " + pollingLoopFilter
+ + " is not a valid Polling Loop Filter.");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
* A valid AID according to ISO/IEC 7816-4:
* <ul>
* <li>Has >= 5 bytes and <=16 bytes (>=10 hex chars and <= 32 hex chars)
diff --git a/nfc/lint-baseline.xml b/nfc/lint-baseline.xml
new file mode 100644
index 000000000000..1dfdd29e480a
--- /dev/null
+++ b/nfc/lint-baseline.xml
@@ -0,0 +1,213 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 8.4.0-alpha01" type="baseline" client="" dependencies="true" name="" variant="all" version="8.4.0-alpha01">
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `new android.nfc.cardemulation.AidGroup`"
+ errorLine1=" AidGroup aidGroup = new AidGroup(aids, category);"
+ errorLine2=" ~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="377"
+ column="29"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getAids`"
+ errorLine1=" return (group != null ? group.getAids() : null);"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="537"
+ column="43"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.AidGroup#getAids`"
+ errorLine1=" return (group != null ? group.getAids() : null);"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="547"
+ column="47"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`"
+ errorLine1=" return (serviceInfo != null ? serviceInfo.getAids() : null);"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="714"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getAids`"
+ errorLine1=" return (serviceInfo != null ? serviceInfo.getAids() : null);"
+ errorLine2=" ~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="724"
+ column="59"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
+ errorLine1=" if (!serviceInfo.isOnHost()) {"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="755"
+ column="34"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+ errorLine1=" return serviceInfo.getOffHostSecureElement() == null ?"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="756"
+ column="40"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+ errorLine1=' "OffHost" : serviceInfo.getOffHostSecureElement();'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="757"
+ column="53"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#isOnHost`"
+ errorLine1=" if (!serviceInfo.isOnHost()) {"
+ errorLine2=" ~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="772"
+ column="38"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+ errorLine1=" return serviceInfo.getOffHostSecureElement() == null ?"
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="773"
+ column="44"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getOffHostSecureElement`"
+ errorLine1=' "Offhost" : serviceInfo.getOffHostSecureElement();'
+ errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="774"
+ column="57"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getDescription`"
+ errorLine1=" return (serviceInfo != null ? serviceInfo.getDescription() : null);"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="798"
+ column="55"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.nfc.cardemulation.ApduServiceInfo#getDescription`"
+ errorLine1=" return (serviceInfo != null ? serviceInfo.getDescription() : null);"
+ errorLine2=" ~~~~~~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="808"
+ column="59"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+ errorLine1=" if (!activity.isResumed()) {"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="1032"
+ column="23"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+ errorLine1=" if (!activity.isResumed()) {"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/CardEmulation.java"
+ line="1066"
+ column="23"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+ errorLine1=" resumed = activity.isResumed();"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/NfcActivityManager.java"
+ line="124"
+ column="32"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+ errorLine1=" if (!activity.isResumed()) {"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/NfcAdapter.java"
+ line="2457"
+ column="23"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+ errorLine1=" if (!activity.isResumed()) {"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java"
+ line="315"
+ column="23"/>
+ </issue>
+
+ <issue
+ id="NewApi"
+ message="Call requires API level 35 (current min is 34): `android.app.Activity#isResumed`"
+ errorLine1=" if (!activity.isResumed()) {"
+ errorLine2=" ~~~~~~~~~">
+ <location
+ file="frameworks/base/nfc/java/android/nfc/cardemulation/NfcFCardEmulation.java"
+ line="351"
+ column="23"/>
+ </issue>
+
+</issues> \ No newline at end of file
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index 2da8c8c69ff8..221ca4fd1c66 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -32,6 +32,7 @@ import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Flags;
import android.os.Process;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
@@ -131,7 +132,7 @@ public class UninstallAlertDialogFragment extends DialogFragment implements
final boolean isUpdate =
((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
final boolean isArchive =
- android.content.pm.Flags.archiving() && (
+ isArchivingEnabled() && (
(dialogInfo.deleteFlags & PackageManager.DELETE_ARCHIVE) != 0);
final UserHandle myUserHandle = Process.myUserHandle();
UserManager userManager = getContext().getSystemService(UserManager.class);
@@ -242,6 +243,11 @@ public class UninstallAlertDialogFragment extends DialogFragment implements
return dialogBuilder.create();
}
+ private static boolean isArchivingEnabled() {
+ return android.content.pm.Flags.archiving()
+ || SystemProperties.getBoolean("pm.archiving.enabled", false);
+ }
+
private boolean isCloneProfile(UserHandle userHandle) {
UserManager customUserManager = getContext()
.createContextAsUser(UserHandle.of(userHandle.getIdentifier()), 0)
diff --git a/packages/SettingsLib/ActionBarShadow/Android.bp b/packages/SettingsLib/ActionBarShadow/Android.bp
index 6f9445874fce..77cbb00427dc 100644
--- a/packages/SettingsLib/ActionBarShadow/Android.bp
+++ b/packages/SettingsLib/ActionBarShadow/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibActionBarShadow",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/ActionButtonsPreference/Android.bp b/packages/SettingsLib/ActionButtonsPreference/Android.bp
index 122855561751..c36b82d175e2 100644
--- a/packages/SettingsLib/ActionButtonsPreference/Android.bp
+++ b/packages/SettingsLib/ActionButtonsPreference/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibActionButtonsPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/ActivityEmbedding/Android.bp b/packages/SettingsLib/ActivityEmbedding/Android.bp
index 41de29a93e51..838a9e505ece 100644
--- a/packages/SettingsLib/ActivityEmbedding/Android.bp
+++ b/packages/SettingsLib/ActivityEmbedding/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibActivityEmbedding",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/AdaptiveIcon/Android.bp b/packages/SettingsLib/AdaptiveIcon/Android.bp
index 044ba872f3e5..67b6fb5f2ed9 100644
--- a/packages/SettingsLib/AdaptiveIcon/Android.bp
+++ b/packages/SettingsLib/AdaptiveIcon/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibAdaptiveIcon",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 5da4b9518a31..c2cb75709b45 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -9,6 +9,9 @@ package {
android_library {
name: "SettingsLib",
+ defaults: [
+ "SettingsLintDefaults",
+ ],
static_libs: [
"androidx.localbroadcastmanager_localbroadcastmanager",
@@ -60,8 +63,15 @@ android_library {
"src/**/*.java",
"src/**/*.kt",
],
+}
+
+// defaults for lint option
+java_defaults {
+ name: "SettingsLintDefaults",
lint: {
- extra_check_modules: ["SettingsLibLintChecker"],
+ extra_check_modules: [
+ "SettingsLibLintChecker",
+ ],
},
}
diff --git a/packages/SettingsLib/AppPreference/Android.bp b/packages/SettingsLib/AppPreference/Android.bp
index 69b9d44fe16f..c5b2ef686688 100644
--- a/packages/SettingsLib/AppPreference/Android.bp
+++ b/packages/SettingsLib/AppPreference/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibAppPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/BannerMessagePreference/Android.bp b/packages/SettingsLib/BannerMessagePreference/Android.bp
index da91344242a1..07290de8661e 100644
--- a/packages/SettingsLib/BannerMessagePreference/Android.bp
+++ b/packages/SettingsLib/BannerMessagePreference/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibBannerMessagePreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/BarChartPreference/Android.bp b/packages/SettingsLib/BarChartPreference/Android.bp
index be1e0cf8ab4f..448ed56a7f28 100644
--- a/packages/SettingsLib/BarChartPreference/Android.bp
+++ b/packages/SettingsLib/BarChartPreference/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibBarChartPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp
index 35572fad55a2..0382829b2652 100644
--- a/packages/SettingsLib/ButtonPreference/Android.bp
+++ b/packages/SettingsLib/ButtonPreference/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibButtonPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
index 70f7554d5e53..87ec0b8d46fb 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibCollapsingToolbarBaseActivity",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/DisplayUtils/Android.bp b/packages/SettingsLib/DisplayUtils/Android.bp
index eab35a11d7d6..279bb70d81bf 100644
--- a/packages/SettingsLib/DisplayUtils/Android.bp
+++ b/packages/SettingsLib/DisplayUtils/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibDisplayUtils",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/EntityHeaderWidgets/Android.bp b/packages/SettingsLib/EntityHeaderWidgets/Android.bp
index 17b662c60227..83f81c60c856 100644
--- a/packages/SettingsLib/EntityHeaderWidgets/Android.bp
+++ b/packages/SettingsLib/EntityHeaderWidgets/Android.bp
@@ -10,13 +10,16 @@ package {
android_library {
name: "SettingsLibEntityHeaderWidgets",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
static_libs: [
- "androidx.annotation_annotation",
- "SettingsLibSettingsTheme"
+ "androidx.annotation_annotation",
+ "SettingsLibSettingsTheme",
],
sdk_version: "system_current",
diff --git a/packages/SettingsLib/FooterPreference/Android.bp b/packages/SettingsLib/FooterPreference/Android.bp
index b45cd65467d2..d1ad80d5a4d7 100644
--- a/packages/SettingsLib/FooterPreference/Android.bp
+++ b/packages/SettingsLib/FooterPreference/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibFooterPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/HelpUtils/Android.bp b/packages/SettingsLib/HelpUtils/Android.bp
index 041fce254b72..284106e96fda 100644
--- a/packages/SettingsLib/HelpUtils/Android.bp
+++ b/packages/SettingsLib/HelpUtils/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibHelpUtils",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/IllustrationPreference/Android.bp b/packages/SettingsLib/IllustrationPreference/Android.bp
index 4d4759b99f15..6407810367cf 100644
--- a/packages/SettingsLib/IllustrationPreference/Android.bp
+++ b/packages/SettingsLib/IllustrationPreference/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibIllustrationPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/LayoutPreference/Android.bp b/packages/SettingsLib/LayoutPreference/Android.bp
index 53ded2385634..8cf636ac8de3 100644
--- a/packages/SettingsLib/LayoutPreference/Android.bp
+++ b/packages/SettingsLib/LayoutPreference/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibLayoutPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/MainSwitchPreference/Android.bp b/packages/SettingsLib/MainSwitchPreference/Android.bp
index 010a6ce9d4d9..b984aaf050d5 100644
--- a/packages/SettingsLib/MainSwitchPreference/Android.bp
+++ b/packages/SettingsLib/MainSwitchPreference/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibMainSwitchPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/ProfileSelector/Android.bp b/packages/SettingsLib/ProfileSelector/Android.bp
index 155ed2e091f8..6dc07b29a510 100644
--- a/packages/SettingsLib/ProfileSelector/Android.bp
+++ b/packages/SettingsLib/ProfileSelector/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibProfileSelector",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/RestrictedLockUtils/Android.bp b/packages/SettingsLib/RestrictedLockUtils/Android.bp
index 3b04bd99e0f4..8d722eba49bf 100644
--- a/packages/SettingsLib/RestrictedLockUtils/Android.bp
+++ b/packages/SettingsLib/RestrictedLockUtils/Android.bp
@@ -16,6 +16,9 @@ filegroup {
android_library {
name: "SettingsLibRestrictedLockUtils",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/SchedulesProvider/Android.bp b/packages/SettingsLib/SchedulesProvider/Android.bp
index 22e4e94b80b1..c0fc741e7447 100644
--- a/packages/SettingsLib/SchedulesProvider/Android.bp
+++ b/packages/SettingsLib/SchedulesProvider/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibSchedulesProvider",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/SearchProvider/Android.bp b/packages/SettingsLib/SearchProvider/Android.bp
index c385d385dcc9..61ed65cbe46f 100644
--- a/packages/SettingsLib/SearchProvider/Android.bp
+++ b/packages/SettingsLib/SearchProvider/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibSearchProvider",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
index 702387ecadab..2fe446d24b34 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibSelectorWithWidgetPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/SettingsSpinner/Android.bp b/packages/SettingsLib/SettingsSpinner/Android.bp
index 0eec50563a75..8fed61fd3f13 100644
--- a/packages/SettingsLib/SettingsSpinner/Android.bp
+++ b/packages/SettingsLib/SettingsSpinner/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibSettingsSpinner",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/SettingsTransition/Android.bp b/packages/SettingsLib/SettingsTransition/Android.bp
index 06493c056203..e04af6c1ab11 100644
--- a/packages/SettingsLib/SettingsTransition/Android.bp
+++ b/packages/SettingsLib/SettingsTransition/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibSettingsTransition",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 8b136da04405..6f9556f91bd6 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@ val androidTop: String = File(rootDir, "../../../../..").canonicalPath
allprojects {
extra["androidTop"] = androidTop
- extra["jetpackComposeVersion"] = "1.6.0-beta02"
+ extra["jetpackComposeVersion"] = "1.6.0-rc01"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index ed9028472fd0..df5644b8aad0 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -74,8 +74,7 @@
android:exported="false">
</provider>
<activity
- android:name="com.android.settingslib.spa.gallery.SpaDialogActivity"
- android:excludeFromRecents="true"
+ android:name="com.android.settingslib.spa.gallery.GalleryDialogActivity"
android:exported="true"
android:theme="@style/Theme.SpaLib.Dialog">
</activity>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDialogActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDialogActivity.kt
new file mode 100644
index 000000000000..e22ed355bff1
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDialogActivity.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery
+
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import com.android.settingslib.spa.SpaBaseDialogActivity
+import com.android.settingslib.spa.widget.dialog.AlertDialogButton
+import com.android.settingslib.spa.widget.dialog.SettingsAlertDialogWithIcon
+
+class GalleryDialogActivity : SpaBaseDialogActivity() {
+ @Composable
+ override fun Content() {
+ SettingsAlertDialogWithIcon(
+ onDismissRequest = { finish() },
+ confirmButton = AlertDialogButton("confirm") { finish() },
+ dismissButton = AlertDialogButton("dismiss") { finish() },
+ title = "title",
+ text = {
+ Text(
+ "text",
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center
+ )
+ }
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaDialogActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaDialogActivity.kt
deleted file mode 100644
index 8b80fe241639..000000000000
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaDialogActivity.kt
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.settingslib.spa.gallery
-
-import android.os.Bundle
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.compose.foundation.layout.width
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.WarningAmber
-import androidx.compose.material3.AlertDialog
-import androidx.compose.material3.Button
-import androidx.compose.material3.Icon
-import androidx.compose.material3.OutlinedButton
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.vector.ImageVector
-import com.android.settingslib.spa.framework.common.LogCategory
-import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.widget.dialog.getDialogWidth
-
-
-class SpaDialogActivity : ComponentActivity() {
- private val spaEnvironment get() = SpaEnvironmentFactory.instance
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
- setContent {
- SettingsTheme {
- Content()
- }
- }
- }
-
- @Composable
- fun Content() {
- var openAlertDialog by remember { mutableStateOf(false) }
- AlertDialog(openAlertDialog)
- LaunchedEffect(key1 = Unit) {
- openAlertDialog = true
- }
- }
-
- @Composable
- fun AlertDialog(openAlertDialog: Boolean) {
- when {
- openAlertDialog -> {
- AlertDialogExample(
- onDismissRequest = { finish() },
- onConfirmation = { finish() },
- dialogTitle = intent.getStringExtra(DIALOG_TITLE) ?: DIALOG_TITLE,
- dialogText = intent.getStringExtra(DIALOG_TEXT) ?: DIALOG_TEXT,
- icon = Icons.Default.WarningAmber
- )
- }
- }
- }
-
- @Composable
- fun AlertDialogExample(
- onDismissRequest: () -> Unit,
- onConfirmation: () -> Unit,
- dialogTitle: String,
- dialogText: String,
- icon: ImageVector,
- ) {
- AlertDialog(
- modifier = Modifier.width(getDialogWidth()),
- icon = {
- Icon(icon, contentDescription = null)
- },
- title = {
- Text(text = dialogTitle)
- },
- text = {
- Text(text = dialogText)
- },
- onDismissRequest = {
- onDismissRequest()
- },
- dismissButton = {
- OutlinedButton(
- onClick = {
- onDismissRequest()
- }
- ) {
- Text(intent.getStringExtra(DISMISS_TEXT) ?: DISMISS_TEXT)
- }
- },
- confirmButton = {
- Button(
- onClick = {
- onConfirmation()
- },
- ) {
- Text(intent.getStringExtra(CONFIRM_TEXT) ?: CONFIRM_TEXT)
- }
- }
- )
- }
-
- companion object {
- private const val TAG = "SpaDialogActivity"
- private const val DIALOG_TITLE = "dialogTitle"
- private const val DIALOG_TEXT = "dialogText"
- private const val CONFIRM_TEXT = "confirmText"
- private const val DISMISS_TEXT = "dismissText"
- }
-}
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 9703c347859c..1f78a9c3ac07 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
#
[versions]
-agp = "8.2.0"
+agp = "8.2.1"
compose-compiler = "1.5.1"
dexmaker-mockito = "2.28.3"
jvm = "17"
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index 7eccfe5ed508..618dc37037aa 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -57,13 +57,13 @@ dependencies {
api("androidx.slice:slice-builders:1.1.0-alpha02")
api("androidx.slice:slice-core:1.1.0-alpha02")
api("androidx.slice:slice-view:1.1.0-alpha02")
- api("androidx.compose.material3:material3:1.2.0-alpha12")
+ api("androidx.compose.material3:material3:1.2.0-beta02")
api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
api("androidx.lifecycle:lifecycle-livedata-ktx")
api("androidx.lifecycle:lifecycle-runtime-compose")
- api("androidx.navigation:navigation-compose:2.7.4")
+ api("androidx.navigation:navigation-compose:2.7.6")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.7.0-alpha03")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaBaseDialogActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaBaseDialogActivity.kt
new file mode 100644
index 000000000000..dfb780af214b
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/SpaBaseDialogActivity.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.settingslib.spa
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+
+abstract class SpaBaseDialogActivity : ComponentActivity() {
+ private val spaEnvironment get() = SpaEnvironmentFactory.instance
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
+ setContent {
+ SettingsTheme {
+ Content()
+ }
+ }
+ }
+
+ @Composable
+ abstract fun Content()
+
+ companion object {
+ private const val TAG = "SpaBaseDialogActivity"
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index 5605485c4b84..da1ee77bcbfb 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -22,12 +22,14 @@ import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.VisibleForTesting
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
import androidx.core.view.WindowCompat
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavGraph.Companion.findStartDestination
@@ -133,6 +135,7 @@ private fun NavControllerWrapperImpl.NavContent(
NavHost(
navController = navController,
startDestination = NullPageProvider.name,
+ modifier = Modifier.fillMaxSize(),
) {
composable(NullPageProvider.name) {}
for (spp in allProvider) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index 81bee5ec0b94..0281ab817340 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -86,7 +86,7 @@ fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): Settings
)
}
-object NullPageProvider : SettingsPageProvider {
+internal object NullPageProvider : SettingsPageProvider {
override val name = NULL_PAGE_NAME
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt
index 192b12500978..93ad644bf5de 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavGraphBuilder.kt
@@ -30,6 +30,7 @@ import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDeepLink
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
+import com.android.settingslib.spa.framework.common.NullPageProvider
/**
* Add the [Composable] to the [NavGraphBuilder] with animation
@@ -49,11 +50,13 @@ internal fun NavGraphBuilder.animatedComposable(
arguments = arguments,
deepLinks = deepLinks,
enterTransition = {
- slideIntoContainer(
- towards = AnimatedContentTransitionScope.SlideDirection.Start,
- animationSpec = slideInEffect,
- initialOffset = offsetFunc,
- ) + fadeIn(animationSpec = fadeInEffect)
+ if (initialState.destination.route != NullPageProvider.name) {
+ slideIntoContainer(
+ towards = AnimatedContentTransitionScope.SlideDirection.Start,
+ animationSpec = slideInEffect,
+ initialOffset = offsetFunc,
+ ) + fadeIn(animationSpec = fadeInEffect)
+ } else null
},
exitTransition = {
slideOutOfContainer(
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 8ffd799385ba..de080e3d8ef4 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
@@ -102,8 +102,8 @@ private fun AlertDialogPresenter.SettingsAlertDialog(
fun getDialogWidth(): Dp {
val configuration = LocalConfiguration.current
return configuration.screenWidthDp.dp * when (configuration.orientation) {
- Configuration.ORIENTATION_LANDSCAPE -> 0.6f
- else -> 0.8f
+ Configuration.ORIENTATION_LANDSCAPE -> 0.65f
+ else -> 0.85f
}
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
new file mode 100644
index 000000000000..1695e4f33915
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogWithIcon.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.dialog
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.WarningAmber
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.window.DialogProperties
+
+@Composable
+fun SettingsAlertDialogWithIcon(
+ onDismissRequest: () -> Unit,
+ confirmButton: AlertDialogButton?,
+ dismissButton: AlertDialogButton?,
+ title: String?,
+ text: @Composable (() -> Unit)?,
+) {
+ AlertDialog(
+ onDismissRequest = onDismissRequest,
+ icon = { Icon(Icons.Default.WarningAmber, contentDescription = null) },
+ modifier = Modifier.width(getDialogWidth()),
+ confirmButton = {
+ confirmButton?.let {
+ Button(
+ onClick = {
+ it.onClick()
+ },
+ ) {
+ Text(it.text)
+ }
+ }
+ },
+ dismissButton = dismissButton?.let {
+ {
+ OutlinedButton(
+ onClick = {
+ it.onClick()
+ },
+ ) {
+ Text(it.text)
+ }
+ }
+ },
+ title = title?.let {
+ {
+ Text(
+ it,
+ modifier = Modifier.fillMaxWidth(),
+ textAlign = TextAlign.Center
+ )
+ }
+ },
+ text = text?.let {
+ {
+ Column(Modifier.verticalScroll(rememberScrollState())) {
+ text()
+ }
+ }
+ },
+ properties = DialogProperties(usePlatformDefaultWidth = false),
+ )
+} \ No newline at end of file
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index abeffece2cd0..0a98791e8a6a 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -24,6 +24,7 @@ import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.ApplicationInfoFlags
import android.content.pm.ResolveInfo
+import android.os.SystemProperties
import com.android.internal.R
import com.android.settingslib.spaprivileged.framework.common.userManager
import kotlinx.coroutines.async
@@ -110,7 +111,7 @@ class AppListRepositoryImpl(
): List<ApplicationInfo> {
val disabledComponentsFlag = (PackageManager.MATCH_DISABLED_COMPONENTS or
PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
- val archivedPackagesFlag: Long = if (featureFlags.archiving())
+ val archivedPackagesFlag: Long = if (isArchivingEnabled(featureFlags))
PackageManager.MATCH_ARCHIVED_PACKAGES else 0L
val regularFlags = ApplicationInfoFlags.of(
disabledComponentsFlag or
@@ -148,6 +149,9 @@ class AppListRepositoryImpl(
}
}
+ private fun isArchivingEnabled(featureFlags: FeatureFlags) =
+ featureFlags.archiving() || SystemProperties.getBoolean("pm.archiving.enabled", false)
+
override fun showSystemPredicate(
userIdFlow: Flow<Int>,
showSystemFlow: Flow<Boolean>,
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 1c830c1c5b06..74b556ea106e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -162,6 +162,7 @@ internal fun <T : AppRecord> TogglePermissionAppListModel<T>.TogglePermissionApp
uid = checkNotNull(applicationInfo).uid,
packageName = packageName) })
RestrictedSwitchPreference(switchModel, restrictions, restrictionsProviderFactory)
+ InfoPageAdditionalContent(record, isAllowed)
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
index 916d83af3f8f..3f7a8526839f 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
@@ -77,6 +77,9 @@ interface TogglePermissionAppListModel<T : AppRecord> {
* Sets whether the permission is allowed for the given app.
*/
fun setAllowed(record: T, newAllowed: Boolean)
+
+ @Composable
+ fun InfoPageAdditionalContent(record: T, isAllowed: () -> Boolean?){}
}
interface TogglePermissionAppListProvider {
diff --git a/packages/SettingsLib/Tile/Android.bp b/packages/SettingsLib/Tile/Android.bp
index 19c59dd221c2..54b97487c3cd 100644
--- a/packages/SettingsLib/Tile/Android.bp
+++ b/packages/SettingsLib/Tile/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibTile",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
diff --git a/packages/SettingsLib/TopIntroPreference/Android.bp b/packages/SettingsLib/TopIntroPreference/Android.bp
index 77b7ac1246bd..e70201b0feb7 100644
--- a/packages/SettingsLib/TopIntroPreference/Android.bp
+++ b/packages/SettingsLib/TopIntroPreference/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibTopIntroPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/TwoTargetPreference/Android.bp b/packages/SettingsLib/TwoTargetPreference/Android.bp
index 5aa906ec0ab3..70d9630d1865 100644
--- a/packages/SettingsLib/TwoTargetPreference/Android.bp
+++ b/packages/SettingsLib/TwoTargetPreference/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibTwoTargetPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/UsageProgressBarPreference/Android.bp b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
index 4cc90ccbfe80..0a83aabfce7b 100644
--- a/packages/SettingsLib/UsageProgressBarPreference/Android.bp
+++ b/packages/SettingsLib/UsageProgressBarPreference/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibUsageProgressBarPreference",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/Utils/Android.bp b/packages/SettingsLib/Utils/Android.bp
index d5a56c86431f..5d2aef780385 100644
--- a/packages/SettingsLib/Utils/Android.bp
+++ b/packages/SettingsLib/Utils/Android.bp
@@ -10,6 +10,9 @@ package {
android_library {
name: "SettingsLibUtils",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp
index 390c9d2e98de..1f8d1dde15df 100644
--- a/packages/SettingsLib/search/Android.bp
+++ b/packages/SettingsLib/search/Android.bp
@@ -17,6 +17,9 @@ java_library {
android_library {
name: "SettingsLib-search",
use_resource_processor: true,
+ defaults: [
+ "SettingsLintDefaults",
+ ],
static_libs: [
"SettingsLib-search-interface",
],
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 071afaf18598..f7f06739d4b2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -117,6 +117,12 @@ public class BluetoothUtils {
}
}
+ if (cachedDevice.isHearingAidDevice()) {
+ return new Pair<>(getBluetoothDrawable(context,
+ com.android.internal.R.drawable.ic_bt_hearing_aid),
+ context.getString(R.string.bluetooth_talkback_hearing_aids));
+ }
+
List<LocalBluetoothProfile> profiles = cachedDevice.getProfiles();
int resId = 0;
for (LocalBluetoothProfile profile : profiles) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 032a8381a7b5..560bc467ca8e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -412,6 +412,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
public void setHearingAidInfo(HearingAidInfo hearingAidInfo) {
mHearingAidInfo = hearingAidInfo;
+ dispatchAttributesChanged();
}
public HearingAidInfo getHearingAidInfo() {
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 7409eea2cd51..f7ec80b041e9 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
@@ -16,6 +16,7 @@
package com.android.settingslib.bluetooth;
import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -87,6 +88,14 @@ public class BluetoothUtilsTest {
}
@Test
+ public void getBtClassDrawableWithDescription_typeHearingAid_returnHearingAidDrawable() {
+ when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
+ BluetoothUtils.getBtClassDrawableWithDescription(mContext, mCachedBluetoothDevice);
+
+ verify(mContext).getDrawable(com.android.internal.R.drawable.ic_bt_hearing_aid);
+ }
+
+ @Test
public void getBtRainbowDrawableWithDescription_normalHeadset_returnAdaptiveIcon() {
when(mBluetoothDevice.getMetadata(
BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)).thenReturn("false".getBytes());
diff --git a/packages/SettingsProvider/TEST_MAPPING b/packages/SettingsProvider/TEST_MAPPING
index 890510ffebe3..0eed2b7490d4 100644
--- a/packages/SettingsProvider/TEST_MAPPING
+++ b/packages/SettingsProvider/TEST_MAPPING
@@ -11,5 +11,10 @@
}
]
}
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsDeviceConfigTestCases"
+ }
]
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 5004f251cfd3..8ae50eb7ffad 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -241,6 +241,8 @@ public class SecureSettings {
Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO,
Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE,
Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME,
+ Settings.Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY,
+ Settings.Secure.BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS,
Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP,
Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER,
Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 0b0e182746e5..285c8c969343 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -387,6 +387,11 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_CODE, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, ANY_STRING_VALIDATOR);
+ VALIDATORS.put(
+ Secure.BLUETOOTH_LE_BROADCAST_IMPROVE_COMPATIBILITY,
+ new DiscreteValueValidator(new String[] {"0", "1"}));
+ VALIDATORS.put(
+ Secure.BLUETOOTH_LE_BROADCAST_FALLBACK_ACTIVE_DEVICE_ADDRESS, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_APP, ANY_STRING_VALIDATOR);
VALIDATORS.put(Secure.CUSTOM_BUGREPORT_HANDLER_USER, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.LOCK_SCREEN_WEATHER_ENABLED, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index b0abf92ffe08..2d442f4c0e6e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1349,6 +1349,26 @@ public class SettingsProvider extends ContentProvider {
final int nameCount = names.size();
HashMap<String, String> flagsToValues = new HashMap<>(names.size());
+ if (Flags.loadAconfigDefaults()) {
+ Map<String, Map<String, String>> allDefaults =
+ settingsState.getAconfigDefaultValues();
+
+ if (allDefaults != null) {
+ if (prefix != null) {
+ String namespace = prefix.substring(0, prefix.length() - 1);
+
+ Map<String, String> namespaceDefaults = allDefaults.get(namespace);
+ if (namespaceDefaults != null) {
+ flagsToValues.putAll(namespaceDefaults);
+ }
+ } else {
+ for (Map<String, String> namespaceDefaults : allDefaults.values()) {
+ flagsToValues.putAll(namespaceDefaults);
+ }
+ }
+ }
+ }
+
for (int i = 0; i < nameCount; i++) {
String name = names.get(i);
Setting setting = settingsState.getSettingLocked(name);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 73c2e22240d3..6f3c88fc8706 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -69,6 +69,7 @@ import java.io.PrintWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -236,6 +237,10 @@ final class SettingsState {
@GuardedBy("mLock")
private int mNextHistoricalOpIdx;
+ @GuardedBy("mLock")
+ @Nullable
+ private Map<String, Map<String, String>> mNamespaceDefaults;
+
public static final int SETTINGS_TYPE_GLOBAL = 0;
public static final int SETTINGS_TYPE_SYSTEM = 1;
public static final int SETTINGS_TYPE_SECURE = 2;
@@ -331,25 +336,21 @@ final class SettingsState {
readStateSyncLocked();
if (Flags.loadAconfigDefaults()) {
- // Only load aconfig defaults if this is the first boot, the XML
- // file doesn't exist yet, or this device is on its first boot after
- // an OTA.
- boolean shouldLoadAconfigValues = isConfigSettingsKey(mKey)
- && (!file.exists()
- || mContext.getPackageManager().isDeviceUpgrading());
- if (shouldLoadAconfigValues) {
+ if (isConfigSettingsKey(mKey)) {
loadAconfigDefaultValuesLocked();
}
}
+
}
}
@GuardedBy("mLock")
private void loadAconfigDefaultValuesLocked() {
+ mNamespaceDefaults = new HashMap<>();
+
for (String fileName : sAconfigTextProtoFilesOnDevice) {
try (FileInputStream inputStream = new FileInputStream(fileName)) {
- byte[] contents = inputStream.readAllBytes();
- loadAconfigDefaultValues(contents);
+ loadAconfigDefaultValues(inputStream.readAllBytes(), mNamespaceDefaults);
} catch (IOException e) {
Slog.e(LOG_TAG, "failed to read protobuf", e);
}
@@ -358,27 +359,21 @@ final class SettingsState {
@VisibleForTesting
@GuardedBy("mLock")
- public void loadAconfigDefaultValues(byte[] fileContents) {
+ public static void loadAconfigDefaultValues(byte[] fileContents,
+ @NonNull Map<String, Map<String, String>> defaultMap) {
try {
- parsed_flags parsedFlags = parsed_flags.parseFrom(fileContents);
-
- if (parsedFlags == null) {
- Slog.e(LOG_TAG, "failed to parse aconfig protobuf");
- return;
- }
-
+ parsed_flags parsedFlags =
+ parsed_flags.parseFrom(fileContents);
for (parsed_flag flag : parsedFlags.getParsedFlagList()) {
- String flagName = flag.getNamespace() + "/"
- + flag.getPackage() + "." + flag.getName();
- String value = flag.getState() == flag_state.ENABLED ? "true" : "false";
-
- Setting existingSetting = getSettingLocked(flagName);
- boolean isDefaultLoaded = existingSetting.getTag() != null
- && existingSetting.getTag().equals(BOOT_LOADED_DEFAULT_TAG);
- if (existingSetting.getValue() == null || isDefaultLoaded) {
- insertSettingLocked(flagName, value, BOOT_LOADED_DEFAULT_TAG,
- false, flag.getPackage());
+ if (!defaultMap.containsKey(flag.getNamespace())) {
+ Map<String, String> defaults = new HashMap<>();
+ defaultMap.put(flag.getNamespace(), defaults);
}
+ String flagName = flag.getNamespace()
+ + "/" + flag.getPackage() + "." + flag.getName();
+ String flagValue = flag.getState() == flag_state.ENABLED
+ ? "true" : "false";
+ defaultMap.get(flag.getNamespace()).put(flagName, flagValue);
}
} catch (IOException e) {
Slog.e(LOG_TAG, "failed to parse protobuf", e);
@@ -443,6 +438,13 @@ final class SettingsState {
return names;
}
+ @Nullable
+ public Map<String, Map<String, String>> getAconfigDefaultValues() {
+ synchronized (mLock) {
+ return mNamespaceDefaults;
+ }
+ }
+
// The settings provider must hold its lock when calling here.
public Setting getSettingLocked(String name) {
if (TextUtils.isEmpty(name)) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index ecac5ee18582..edbc0b391b27 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -14,3 +14,11 @@ flag {
bug: "311155098"
is_fixed_read_only: true
}
+
+flag {
+ name: "configurable_font_scale_default"
+ namespace: "large_screen_experiences_app_compat"
+ description: "Whether the font_scale is read from a device dependent configuration file"
+ bug: "319808237"
+ is_fixed_read_only: true
+} \ No newline at end of file
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 24625eaa5e13..e55bbecb67d7 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -30,6 +30,8 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
+import java.util.HashMap;
+import java.util.Map;
public class SettingsStateTest extends AndroidTestCase {
public static final String CRAZY_STRING =
@@ -93,7 +95,6 @@ public class SettingsStateTest extends AndroidTestCase {
SettingsState settingsState = new SettingsState(
getContext(), lock, mSettingsFile, configKey,
SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
-
parsed_flags flags = parsed_flags
.newBuilder()
.addParsedFlag(parsed_flag
@@ -117,18 +118,13 @@ public class SettingsStateTest extends AndroidTestCase {
.build();
synchronized (lock) {
- settingsState.loadAconfigDefaultValues(flags.toByteArray());
- settingsState.persistSettingsLocked();
- }
- settingsState.waitForHandler();
+ Map<String, Map<String, String>> defaults = new HashMap<>();
+ settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
+ Map<String, String> namespaceDefaults = defaults.get("test_namespace");
+ assertEquals(2, namespaceDefaults.keySet().size());
- synchronized (lock) {
- assertEquals("false",
- settingsState.getSettingLocked(
- "test_namespace/com.android.flags.flag1").getValue());
- assertEquals("true",
- settingsState.getSettingLocked(
- "test_namespace/com.android.flags.flag2").getValue());
+ assertEquals("false", namespaceDefaults.get("test_namespace/com.android.flags.flag1"));
+ assertEquals("true", namespaceDefaults.get("test_namespace/com.android.flags.flag2"));
}
}
@@ -150,21 +146,18 @@ public class SettingsStateTest extends AndroidTestCase {
.build();
synchronized (lock) {
- settingsState.loadAconfigDefaultValues(flags.toByteArray());
- settingsState.persistSettingsLocked();
- }
- settingsState.waitForHandler();
+ Map<String, Map<String, String>> defaults = new HashMap<>();
+ settingsState.loadAconfigDefaultValues(flags.toByteArray(), defaults);
- synchronized (lock) {
- assertEquals(null,
- settingsState.getSettingLocked(
- "test_namespace/com.android.flags.flag1").getValue());
+ Map<String, String> namespaceDefaults = defaults.get("test_namespace");
+ assertEquals(null, namespaceDefaults);
}
}
public void testInvalidAconfigProtoDoesNotCrash() {
+ Map<String, Map<String, String>> defaults = new HashMap<>();
SettingsState settingsState = getSettingStateObject();
- settingsState.loadAconfigDefaultValues("invalid protobuf".getBytes());
+ settingsState.loadAconfigDefaultValues("invalid protobuf".getBytes(), defaults);
}
public void testIsBinary() {
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 507d9c467d68..db1ca95fd83e 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -559,6 +559,9 @@
<!-- Permission required for CTS test - android.server.biometrics -->
<uses-permission android:name="android.permission.TEST_BIOMETRIC" />
+ <!-- Permission required for CTS test - android.server.biometrics -->
+ <uses-permission android:name="android.permission.MANAGE_BIOMETRIC_DIALOG" />
+
<!-- Permissions required for CTS test - NotificationManagerTest -->
<uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
@@ -748,6 +751,7 @@
<uses-permission android:name="android.permission.BIND_TELECOM_CONNECTION_SERVICE" />
<uses-permission android:name="android.permission.MODIFY_CELL_BROADCASTS" />
<uses-permission android:name="android.permission.SATELLITE_COMMUNICATION" />
+ <uses-permission android:name="android.permission.ACCESS_LAST_KNOWN_CELL_ID" />
<!-- Permission required for CTS test - CtsPersistentDataBlockManagerTestCases -->
<uses-permission android:name="android.permission.ACCESS_PDB_STATE" />
@@ -891,6 +895,9 @@
<!-- Permission required for Cts test - CtsNotificationTestCases -->
<uses-permission android:name="android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS" />
+ <!-- Permission required for BinaryTransparencyService shell API and host test -->
+ <uses-permission android:name="android.permission.GET_BACKGROUND_INSTALLED_PACKAGES" />
+
<application
android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 7443e4ccf79e..168e6e003dc6 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -355,6 +355,8 @@
<uses-permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT" />
+ <uses-permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE" />
+
<!-- Listen to (dis-)connection of external displays and enable / disable them. -->
<uses-permission android:name="android.permission.MANAGE_DISPLAYS" />
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index f7b1a26c9df9..7ba889bc8fee 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -36,3 +36,10 @@ flag {
description: "Animates the floating menu's transition between curved and jagged edges."
bug: "281140482"
}
+
+flag {
+ name: "create_windowless_window_magnifier"
+ namespace: "accessibility"
+ description: "Uses SurfaceControlViewHost to create the magnifier for window magnification."
+ bug: "280992417"
+}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index c23a49c68363..323613077a70 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -15,6 +15,13 @@ flag {
}
flag {
+ name: "notification_async_group_header_inflation"
+ namespace: "systemui"
+ description: "Inflates the notification group summary header views from the background thread."
+ bug: "217799515"
+}
+
+flag {
name: "notification_async_hybrid_view_inflation"
namespace: "systemui"
description: "Inflates hybrid (single-line) notification views from the background thread."
@@ -343,3 +350,10 @@ flag {
description: "Relocate Smartspace to bottom of the Lock Screen"
bug: "316212788"
}
+
+flag {
+ name: "pin_input_field_styled_focus_state"
+ namespace: "systemui"
+ description: "Enables styled focus states on pin input field if keyboard is connected"
+ bug: "316106516"
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index b70486473672..c073b79ba5a3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -67,8 +67,9 @@ fun CommunalContainer(
// Don't show hub mode UI if keyguard is not present. This is important since we're in the
// shade, which can be opened from many locations.
val isKeyguardShowing by viewModel.isKeyguardVisible.collectAsState(initial = false)
+ val isCommunalAvailable by viewModel.isCommunalAvailable.collectAsState()
- if (!isKeyguardShowing) {
+ if (!isKeyguardShowing || !isCommunalAvailable) {
return
}
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 91a4d2e01e90..a390305b144e 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
@@ -20,6 +20,7 @@ import android.appwidget.AppWidgetHostView
import android.os.Bundle
import android.util.SizeF
import android.widget.FrameLayout
+import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
@@ -38,6 +39,7 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
+import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -58,7 +60,9 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -67,8 +71,10 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.boundsInWindow
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
@@ -83,9 +89,14 @@ import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.compose.ui.window.Popup
+import androidx.core.view.setPadding
import com.android.compose.theme.LocalAndroidColorScheme
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.ui.compose.Dimensions.CardOutlineWidth
+import com.android.systemui.communal.ui.compose.extensions.allowGestures
+import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
+import com.android.systemui.communal.ui.compose.extensions.observeTapsWithoutConsuming
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
import com.android.systemui.res.R
@@ -104,22 +115,59 @@ fun CommunalHub(
var toolbarSize: IntSize? by remember { mutableStateOf(null) }
var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
var isDraggingToRemove by remember { mutableStateOf(false) }
+ val gridState = rememberLazyGridState()
+ val contentListState = rememberContentListState(communalContent, viewModel)
+ val reorderingWidgets by viewModel.reorderingWidgets.collectAsState()
+ val selectedIndex = viewModel.selectedIndex.collectAsState()
+ val removeButtonEnabled by remember {
+ derivedStateOf { selectedIndex.value != null || reorderingWidgets }
+ }
+
+ val contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize)
+ val contentOffset = beforeContentPadding(contentPadding).toOffset()
Box(
modifier =
- modifier.fillMaxSize().background(LocalAndroidColorScheme.current.outlineVariant),
+ modifier
+ .fillMaxSize()
+ .background(LocalAndroidColorScheme.current.outlineVariant)
+ .pointerInput(gridState, contentOffset, contentListState) {
+ // If not in edit mode, don't allow selecting items.
+ if (!viewModel.isEditMode) return@pointerInput
+ observeTapsWithoutConsuming { offset ->
+ val adjustedOffset = offset - contentOffset
+ val index =
+ gridState.layoutInfo.visibleItemsInfo
+ .firstItemAtOffset(adjustedOffset)
+ ?.index
+ val newIndex =
+ if (index?.let(contentListState::isItemEditable) == true) {
+ index
+ } else {
+ null
+ }
+ viewModel.setSelectedIndex(newIndex)
+ }
+ },
) {
CommunalHubLazyGrid(
communalContent = communalContent,
viewModel = viewModel,
- contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize),
+ contentPadding = contentPadding,
+ contentOffset = contentOffset,
setGridCoordinates = { gridCoordinates = it },
- updateDragPositionForRemove = {
+ updateDragPositionForRemove = { offset ->
isDraggingToRemove =
- checkForDraggingToRemove(it, removeButtonCoordinates, gridCoordinates)
+ isPointerWithinCoordinates(
+ offset = gridCoordinates?.let { it.positionInWindow() + offset },
+ containerToCheck = removeButtonCoordinates
+ )
isDraggingToRemove
},
onOpenWidgetPicker = onOpenWidgetPicker,
+ gridState = gridState,
+ contentListState = contentListState,
+ selectedIndex = selectedIndex
)
if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
@@ -129,6 +177,14 @@ fun CommunalHub(
setRemoveButtonCoordinates = { removeButtonCoordinates = it },
onEditDone = onEditDone,
onOpenWidgetPicker = onOpenWidgetPicker,
+ onRemoveClicked = {
+ selectedIndex.value?.let { index ->
+ contentListState.onRemove(index)
+ contentListState.onSaveList()
+ viewModel.setSelectedIndex(null)
+ }
+ },
+ removeEnabled = removeButtonEnabled
)
} else {
IconButton(onClick = viewModel::onOpenWidgetEditor) {
@@ -158,16 +214,18 @@ private fun BoxScope.CommunalHubLazyGrid(
communalContent: List<CommunalContentModel>,
viewModel: BaseCommunalViewModel,
contentPadding: PaddingValues,
+ selectedIndex: State<Int?>,
+ contentOffset: Offset,
+ gridState: LazyGridState,
+ contentListState: ContentListState,
setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit,
updateDragPositionForRemove: (offset: Offset) -> Boolean,
onOpenWidgetPicker: (() -> Unit)? = null,
) {
var gridModifier = Modifier.align(Alignment.CenterStart)
- val gridState = rememberLazyGridState()
var list = communalContent
var dragDropState: GridDragDropState? = null
if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
- val contentListState = rememberContentListState(list, viewModel)
list = contentListState.list
// for drag & drop operations within the communal hub grid
dragDropState =
@@ -179,7 +237,7 @@ private fun BoxScope.CommunalHubLazyGrid(
gridModifier =
gridModifier
.fillMaxSize()
- .dragContainer(dragDropState, beforeContentPadding(contentPadding), viewModel)
+ .dragContainer(dragDropState, contentOffset, viewModel)
.onGloballyPositioned { setGridCoordinates(it) }
// for widgets dropped from other activities
val dragAndDropTargetState =
@@ -218,8 +276,10 @@ private fun BoxScope.CommunalHubLazyGrid(
list[index].size.dp().value,
)
if (viewModel.isEditMode && dragDropState != null) {
+ val selected by remember(index) { derivedStateOf { index == selectedIndex.value } }
DraggableItem(
dragDropState = dragDropState,
+ selected = selected,
enabled = list[index] is CommunalContentModel.Widget,
index = index,
size = size
@@ -253,11 +313,19 @@ private fun BoxScope.CommunalHubLazyGrid(
@Composable
private fun Toolbar(
isDraggingToRemove: Boolean,
+ removeEnabled: Boolean,
+ onRemoveClicked: () -> Unit,
setToolbarSize: (toolbarSize: IntSize) -> Unit,
setRemoveButtonCoordinates: (coordinates: LayoutCoordinates) -> Unit,
onOpenWidgetPicker: () -> Unit,
- onEditDone: () -> Unit,
+ onEditDone: () -> Unit
) {
+ val removeButtonAlpha: Float by
+ animateFloatAsState(
+ targetValue = if (removeEnabled) 1f else 0.5f,
+ label = "RemoveButtonAlphaAnimation"
+ )
+
Row(
modifier =
Modifier.fillMaxWidth()
@@ -301,13 +369,18 @@ private fun Toolbar(
}
} else {
OutlinedButton(
- // Button is disabled to make it non-clickable
- enabled = false,
- onClick = {},
- colors = ButtonDefaults.outlinedButtonColors(disabledContentColor = colors.primary),
+ enabled = removeEnabled,
+ onClick = onRemoveClicked,
+ colors =
+ ButtonDefaults.outlinedButtonColors(
+ contentColor = colors.primary,
+ disabledContentColor = colors.primary
+ ),
border = BorderStroke(width = 1.0.dp, color = colors.primary),
contentPadding = Dimensions.ButtonPadding,
- modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) }
+ modifier =
+ Modifier.graphicsLayer { alpha = removeButtonAlpha }
+ .onGloballyPositioned { setRemoveButtonCoordinates(it) }
) {
RemoveButtonContent(spacerModifier)
}
@@ -385,7 +458,7 @@ private fun CommunalContent(
) {
when (model) {
is CommunalContentModel.Widget -> WidgetContent(viewModel, model, size, modifier)
- is CommunalContentModel.WidgetPlaceholder -> WidgetPlaceholderContent(size)
+ is CommunalContentModel.WidgetPlaceholder -> HighlightedItem(size)
is CommunalContentModel.CtaTileInViewMode ->
CtaTileInViewModeContent(viewModel, size, modifier)
is CommunalContentModel.CtaTileInEditMode ->
@@ -396,13 +469,13 @@ private fun CommunalContent(
}
}
-/** Presents a placeholder card for the new widget being dragged and dropping into the grid. */
+/** Creates an empty card used to highlight a particular spot on the grid. */
@Composable
-fun WidgetPlaceholderContent(size: SizeF) {
+fun HighlightedItem(size: SizeF, modifier: Modifier = Modifier) {
Card(
- modifier = Modifier.size(Dp(size.width), Dp(size.height)),
+ modifier = modifier.size(Dp(size.width), Dp(size.height)),
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
- border = BorderStroke(3.dp, LocalAndroidColorScheme.current.tertiaryFixed),
+ border = BorderStroke(CardOutlineWidth, LocalAndroidColorScheme.current.tertiaryFixed),
shape = RoundedCornerShape(16.dp)
) {}
}
@@ -416,7 +489,7 @@ private fun CtaTileInViewModeContent(
) {
val colors = LocalAndroidColorScheme.current
Card(
- modifier = modifier.height(size.height.dp),
+ modifier = modifier.height(size.height.dp).padding(CardOutlineWidth),
colors =
CardDefaults.cardColors(
containerColor = colors.primary,
@@ -488,7 +561,7 @@ private fun CtaTileInEditModeContent(
}
val colors = LocalAndroidColorScheme.current
Card(
- modifier = modifier.height(size.height.dp),
+ modifier = modifier.height(size.height.dp).padding(CardOutlineWidth),
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
border = BorderStroke(1.dp, colors.primary),
shape = RoundedCornerShape(200.dp),
@@ -527,8 +600,9 @@ private fun WidgetContent(
modifier = modifier.height(size.height.dp),
contentAlignment = Alignment.Center,
) {
+ val paddingInPx = with(LocalDensity.current) { CardOutlineWidth.toPx().toInt() }
AndroidView(
- modifier = modifier,
+ modifier = modifier.allowGestures(allowed = !viewModel.isEditMode),
factory = { context ->
// The AppWidgetHostView will inherit the interaction handler from the
// AppWidgetHost. So set the interaction handler here before creating the view, and
@@ -538,9 +612,13 @@ private fun WidgetContent(
model.appWidgetHost.setInteractionHandler(viewModel.getInteractionHandler())
val view =
model.appWidgetHost
- .createView(context, model.appWidgetId, model.providerInfo)
+ .createViewForCommunal(context, model.appWidgetId, model.providerInfo)
.apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
model.appWidgetHost.setInteractionHandler(null)
+ // Remove the extra padding applied to AppWidgetHostView to allow widgets to
+ // occupy the entire box. The added padding is now adjusted to leave only sufficient
+ // space for displaying the outline around the box when the widget is selected.
+ view.setPadding(paddingInPx)
view
},
// For reusing composition in lazy lists.
@@ -616,8 +694,8 @@ private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): Padd
private fun beforeContentPadding(paddingValues: PaddingValues): ContentPaddingInPx {
return with(LocalDensity.current) {
ContentPaddingInPx(
- startPadding = paddingValues.calculateLeftPadding(LayoutDirection.Ltr).toPx(),
- topPadding = paddingValues.calculateTopPadding().toPx()
+ start = paddingValues.calculateLeftPadding(LayoutDirection.Ltr).toPx(),
+ top = paddingValues.calculateTopPadding().toPx()
)
}
}
@@ -626,18 +704,15 @@ private fun beforeContentPadding(paddingValues: PaddingValues): ContentPaddingIn
* Check whether the pointer position that the item is being dragged at is within the coordinates of
* the remove button in the toolbar. Returns true if the item is removable.
*/
-private fun checkForDraggingToRemove(
- offset: Offset,
- removeButtonCoordinates: LayoutCoordinates?,
- gridCoordinates: LayoutCoordinates?,
+private fun isPointerWithinCoordinates(
+ offset: Offset?,
+ containerToCheck: LayoutCoordinates?
): Boolean {
- if (removeButtonCoordinates == null || gridCoordinates == null) {
+ if (offset == null || containerToCheck == null) {
return false
}
- val pointer = gridCoordinates.positionInWindow() + offset
- val removeButton = removeButtonCoordinates.positionInWindow()
- return pointer.x in removeButton.x..removeButton.x + removeButtonCoordinates.size.width &&
- pointer.y in removeButton.y..removeButton.y + removeButtonCoordinates.size.height
+ val container = containerToCheck.boundsInWindow()
+ return container.contains(offset)
}
private fun CommunalContentSize.dp(): Dp {
@@ -648,13 +723,16 @@ private fun CommunalContentSize.dp(): Dp {
}
}
-data class ContentPaddingInPx(val startPadding: Float, val topPadding: Float)
+data class ContentPaddingInPx(val start: Float, val top: Float) {
+ fun toOffset(): Offset = Offset(start, top)
+}
object Dimensions {
val CardWidth = 464.dp
val CardHeightFull = 630.dp
val CardHeightHalf = 307.dp
val CardHeightThird = 199.dp
+ val CardOutlineWidth = 3.dp
val GridHeight = CardHeightFull
val Spacing = 16.dp
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
index 979991d7dc2a..45f98b879dd7 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/ContentListState.kt
@@ -21,12 +21,12 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.toMutableStateList
import com.android.systemui.communal.domain.model.CommunalContentModel
-import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
+import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
@Composable
fun rememberContentListState(
communalContent: List<CommunalContentModel>,
- viewModel: CommunalEditModeViewModel,
+ viewModel: BaseCommunalViewModel,
): ContentListState {
return remember(communalContent) {
ContentListState(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 113822167ca7..a1959532fbb9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -17,6 +17,10 @@
package com.android.systemui.communal.ui.compose
import android.util.SizeF
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress
import androidx.compose.foundation.gestures.scrollBy
@@ -32,6 +36,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
@@ -39,6 +44,7 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.toOffset
import androidx.compose.ui.unit.toSize
import androidx.compose.ui.zIndex
+import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
import com.android.systemui.communal.ui.compose.extensions.plus
import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
import kotlinx.coroutines.CoroutineScope
@@ -109,13 +115,10 @@ internal constructor(
internal fun onDragStart(offset: Offset, contentOffset: Offset) {
state.layoutInfo.visibleItemsInfo
- .firstOrNull { item ->
- // grid item offset is based off grid content container so we need to deduct
- // before content padding from the initial pointer position
- contentListState.isItemEditable(item.index) &&
- (offset.x - contentOffset.x).toInt() in item.offset.x..item.offsetEnd.x &&
- (offset.y - contentOffset.y).toInt() in item.offset.y..item.offsetEnd.y
- }
+ .filter { item -> contentListState.isItemEditable(item.index) }
+ // grid item offset is based off grid content container so we need to deduct
+ // before content padding from the initial pointer position
+ .firstItemAtOffset(offset - contentOffset)
?.apply {
dragStartPointerOffset = offset - this.offset.toOffset()
draggingItemIndex = index
@@ -148,12 +151,11 @@ internal constructor(
val middleOffset = startOffset + (endOffset - startOffset) / 2f
val targetItem =
- state.layoutInfo.visibleItemsInfo.find { item ->
- contentListState.isItemEditable(item.index) &&
- middleOffset.x.toInt() in item.offset.x..item.offsetEnd.x &&
- middleOffset.y.toInt() in item.offset.y..item.offsetEnd.y &&
- draggingItem.index != item.index
- }
+ state.layoutInfo.visibleItemsInfo
+ .asSequence()
+ .filter { item -> contentListState.isItemEditable(item.index) }
+ .filter { item -> draggingItem.index != item.index }
+ .firstItemAtOffset(middleOffset)
if (targetItem != null) {
val scrollToIndex =
@@ -208,32 +210,31 @@ internal constructor(
fun Modifier.dragContainer(
dragDropState: GridDragDropState,
- beforeContentPadding: ContentPaddingInPx,
+ contentOffset: Offset,
viewModel: BaseCommunalViewModel,
): Modifier {
- return pointerInput(dragDropState, beforeContentPadding) {
- detectDragGesturesAfterLongPress(
- onDrag = { change, offset ->
- change.consume()
- dragDropState.onDrag(offset = offset)
- },
- onDragStart = { offset ->
- dragDropState.onDragStart(
- offset,
- Offset(beforeContentPadding.startPadding, beforeContentPadding.topPadding)
- )
- viewModel.onReorderWidgetStart()
- },
- onDragEnd = {
- dragDropState.onDragInterrupted()
- viewModel.onReorderWidgetEnd()
- },
- onDragCancel = {
- dragDropState.onDragInterrupted()
- viewModel.onReorderWidgetCancel()
- }
- )
- }
+ return this.then(
+ pointerInput(dragDropState, contentOffset) {
+ detectDragGesturesAfterLongPress(
+ onDrag = { change, offset ->
+ change.consume()
+ dragDropState.onDrag(offset = offset)
+ },
+ onDragStart = { offset ->
+ dragDropState.onDragStart(offset, contentOffset)
+ viewModel.onReorderWidgetStart()
+ },
+ onDragEnd = {
+ dragDropState.onDragInterrupted()
+ viewModel.onReorderWidgetEnd()
+ },
+ onDragCancel = {
+ dragDropState.onDragInterrupted()
+ viewModel.onReorderWidgetCancel()
+ }
+ )
+ }
+ )
}
/** Wrap LazyGrid item with additional modifier needed for drag and drop. */
@@ -243,6 +244,7 @@ fun LazyGridItemScope.DraggableItem(
dragDropState: GridDragDropState,
index: Int,
enabled: Boolean,
+ selected: Boolean,
size: SizeF,
modifier: Modifier = Modifier,
content: @Composable (isDragging: Boolean) -> Unit
@@ -250,21 +252,31 @@ fun LazyGridItemScope.DraggableItem(
if (!enabled) {
return Box(modifier = modifier) { content(false) }
}
+
val dragging = index == dragDropState.draggingItemIndex
+ val itemAlpha: Float by
+ animateFloatAsState(
+ targetValue = if (dragDropState.isDraggingToRemove) 0.5f else 1f,
+ label = "DraggableItemAlpha"
+ )
val draggingModifier =
if (dragging) {
Modifier.zIndex(1f).graphicsLayer {
translationX = dragDropState.draggingItemOffset.x
translationY = dragDropState.draggingItemOffset.y
- alpha = if (dragDropState.isDraggingToRemove) 0.5f else 1f
+ alpha = itemAlpha
}
} else {
Modifier.animateItemPlacement()
}
Box(modifier) {
- if (dragging) {
- WidgetPlaceholderContent(size)
+ AnimatedVisibility(
+ visible = (dragging || selected) && !dragDropState.isDraggingToRemove,
+ enter = fadeIn(),
+ exit = fadeOut()
+ ) {
+ HighlightedItem(size)
}
Box(modifier = draggingModifier, propagateMinConstraints = true) { content(dragging) }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/LazyGridStateExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/LazyGridStateExt.kt
new file mode 100644
index 000000000000..132093f034bb
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/LazyGridStateExt.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.compose.extensions
+
+import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.unit.toRect
+
+/**
+ * Determine the item at the specified offset, or null if none exist.
+ *
+ * @param offset The offset in pixels, relative to the top start of the grid.
+ */
+fun Iterable<LazyGridItemInfo>.firstItemAtOffset(offset: Offset): LazyGridItemInfo? =
+ firstOrNull { item ->
+ isItemAtOffset(item, offset)
+ }
+
+/**
+ * Determine the item at the specified offset, or null if none exist.
+ *
+ * @param offset The offset in pixels, relative to the top start of the grid.
+ */
+fun Sequence<LazyGridItemInfo>.firstItemAtOffset(offset: Offset): LazyGridItemInfo? =
+ firstOrNull { item ->
+ isItemAtOffset(item, offset)
+ }
+
+private fun isItemAtOffset(item: LazyGridItemInfo, offset: Offset): Boolean {
+ val boundingBox = IntRect(item.offset, item.size)
+ return boundingBox.toRect().contains(offset)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/ModifierExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/ModifierExt.kt
new file mode 100644
index 000000000000..b31008e04593
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/ModifierExt.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.communal.ui.compose.extensions
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.pointer.pointerInput
+
+/** Sets whether gestures are allowed on children of this element. */
+fun Modifier.allowGestures(allowed: Boolean): Modifier =
+ if (allowed) {
+ this
+ } else {
+ this.then(pointerInput(Unit) { consumeAllGestures() })
+ }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.kt
new file mode 100644
index 000000000000..14074944259b
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/extensions/PointerInputScopeExt.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.communal.ui.compose.extensions
+
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.waitForUpOrCancellation
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.PointerInputScope
+import kotlinx.coroutines.coroutineScope
+
+/**
+ * Observe taps without actually consuming them, so child elements can still respond to them. Long
+ * presses are excluded.
+ */
+suspend fun PointerInputScope.observeTapsWithoutConsuming(
+ pass: PointerEventPass = PointerEventPass.Initial,
+ onTap: ((Offset) -> Unit)? = null,
+) = coroutineScope {
+ if (onTap == null) return@coroutineScope
+ awaitEachGesture {
+ awaitFirstDown(pass = pass)
+ val tapTimeout = viewConfiguration.longPressTimeoutMillis
+ val up = withTimeoutOrNull(tapTimeout) { waitForUpOrCancellation(pass = pass) }
+ if (up != null) {
+ onTap(up.position)
+ }
+ }
+}
+
+/** Consume all gestures on the initial pass so that child elements do not receive them. */
+suspend fun PointerInputScope.consumeAllGestures() = coroutineScope {
+ awaitEachGesture {
+ awaitPointerEvent(pass = PointerEventPass.Initial)
+ .changes
+ .forEach(PointerInputChange::consume)
+ }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index 900616f6af89..42fcd1363f11 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -16,23 +16,55 @@
package com.android.systemui.keyguard.ui.composable.section
+import android.content.Context
+import android.view.ViewGroup
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.statusbar.notification.stack.AmbientState
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
+import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
+import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationStackAppearanceViewBinder
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import javax.inject.Inject
class NotificationSection
@Inject
constructor(
+ @Application context: Context,
private val viewModel: NotificationsPlaceholderViewModel,
+ controller: NotificationStackScrollLayoutController,
+ sceneContainerFlags: SceneContainerFlags,
+ sharedNotificationContainer: SharedNotificationContainer,
+ stackScrollLayout: NotificationStackScrollLayout,
+ notificationStackAppearanceViewModel: NotificationStackAppearanceViewModel,
+ ambientState: AmbientState,
) {
+ init {
+ if (sceneContainerFlags.flexiNotifsEnabled()) {
+ (stackScrollLayout.parent as? ViewGroup)?.removeView(stackScrollLayout)
+ sharedNotificationContainer.addNotificationStackScrollLayout(stackScrollLayout)
+
+ NotificationStackAppearanceViewBinder.bind(
+ context,
+ sharedNotificationContainer,
+ notificationStackAppearanceViewModel,
+ ambientState,
+ controller,
+ )
+ }
+ }
+
@Composable
fun SceneScope.Notifications(modifier: Modifier = Modifier) {
NotificationStack(
viewModel = viewModel,
- isScrimVisible = false,
modifier = modifier,
)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 0b26ae96de54..e835d3e576d5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -22,6 +22,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
@@ -43,8 +44,10 @@ import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.height
import com.android.systemui.notifications.ui.composable.Notifications.Form
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import kotlin.math.roundToInt
object Notifications {
object Elements {
@@ -77,32 +80,52 @@ fun SceneScope.HeadsUpNotificationSpace(
)
}
-/** Adds the space where notification stack will appear in the scene. */
+/** Adds the space where notification stack should appear in the scene. */
@Composable
fun SceneScope.NotificationStack(
viewModel: NotificationsPlaceholderViewModel,
- isScrimVisible: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ NotificationPlaceholder(
+ viewModel = viewModel,
+ form = Form.Stack,
+ modifier = modifier,
+ )
+}
+
+/**
+ * Adds the space where notification stack should appear in the scene, with a scrim and nested
+ * scrolling.
+ */
+@Composable
+fun SceneScope.NotificationScrollingStack(
+ viewModel: NotificationsPlaceholderViewModel,
modifier: Modifier = Modifier,
) {
val cornerRadius by viewModel.cornerRadiusDp.collectAsState()
- Box(modifier = modifier) {
- if (isScrimVisible) {
- Box(
- modifier =
- Modifier.element(Notifications.Elements.NotificationScrim)
- .fillMaxSize()
- .graphicsLayer {
- shape = RoundedCornerShape(cornerRadius.dp)
- clip = true
- }
- .background(MaterialTheme.colorScheme.surface)
- )
- }
+ val contentHeight by viewModel.intrinsicContentHeight.collectAsState()
+
+ val expansionFraction by viewModel.expandFraction.collectAsState(0f)
+
+ Box(
+ modifier =
+ modifier
+ .verticalNestedScrollToScene()
+ .fillMaxWidth()
+ .element(Notifications.Elements.NotificationScrim)
+ .graphicsLayer {
+ shape = RoundedCornerShape(cornerRadius.dp)
+ clip = true
+ alpha = expansionFraction
+ }
+ .background(MaterialTheme.colorScheme.surface)
+ .debugBackground(viewModel, Color(0.5f, 0.5f, 0f, 0.2f))
+ ) {
NotificationPlaceholder(
viewModel = viewModel,
form = Form.Stack,
- modifier = Modifier.fillMaxSize(),
+ modifier = Modifier.fillMaxWidth().height { contentHeight.roundToInt() }
)
}
}
@@ -159,6 +182,7 @@ private fun SceneScope.NotificationPlaceholder(
debugLog(viewModel) { "STACK onSizeChanged: size=$size" }
}
.onPlaced { coordinates: LayoutCoordinates ->
+ viewModel.onContentTopChanged(coordinates.positionInWindow().y)
debugLog(viewModel) {
"STACK onPlaced:" +
" size=${coordinates.size}" +
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index bded98d52481..747faabe514b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -25,6 +25,7 @@ import androidx.compose.ui.unit.dp
import com.android.compose.animation.scene.SceneScope
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
+import com.android.systemui.notifications.ui.composable.Notifications
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.Edge
import com.android.systemui.scene.shared.model.SceneKey
@@ -66,6 +67,7 @@ constructor(
modifier: Modifier,
) {
Box(modifier = modifier) {
+ Box(modifier = Modifier.fillMaxSize().element(Notifications.Elements.NotificationScrim))
HeadsUpNotificationSpace(
viewModel = notificationsViewModel,
modifier = Modifier.padding(16.dp).fillMaxSize(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
index 6bb525aa00fb..0c2c5195becc 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -3,12 +3,12 @@ package com.android.systemui.scene.ui.composable.transitions
import androidx.compose.animation.core.tween
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.TransitionBuilder
-import com.android.systemui.notifications.ui.composable.Notifications
-import com.android.systemui.scene.ui.composable.Shade
+import com.android.systemui.qs.ui.composable.QuickSettings
+import com.android.systemui.shade.ui.composable.ShadeHeader
fun TransitionBuilder.goneToShadeTransition() {
spec = tween(durationMillis = 500)
- translate(Shade.rootElementKey, Edge.Top, true)
- fade(Notifications.Elements.NotificationScrim)
+ fractionRange(start = .58f) { fade(ShadeHeader.Elements.CollapsedContent) }
+ translate(QuickSettings.Elements.Content, Edge.Top, true)
}
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 9c0f1fe0ec68..1545372686c9 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
@@ -22,11 +22,9 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
@@ -44,10 +42,12 @@ import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.controls.ui.MediaCarouselController
+import com.android.systemui.media.controls.ui.MediaHierarchyManager
import com.android.systemui.media.controls.ui.MediaHost
+import com.android.systemui.media.controls.ui.MediaHostState
import com.android.systemui.media.controls.ui.composable.MediaCarousel
import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
-import com.android.systemui.notifications.ui.composable.NotificationStack
+import com.android.systemui.notifications.ui.composable.NotificationScrollingStack
import com.android.systemui.qs.ui.composable.QuickSettings
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Direction
@@ -128,6 +128,12 @@ constructor(
modifier = modifier,
)
+ init {
+ mediaHost.expansion = MediaHostState.EXPANDED
+ mediaHost.showsOnlyActiveMedia = true
+ mediaHost.init(MediaHierarchyManager.LOCATION_QQS)
+ }
+
private fun destinationScenes(
up: SceneKey,
): Map<UserAction, SceneModel> {
@@ -148,35 +154,27 @@ private fun SceneScope.ShadeScene(
mediaHost: MediaHost,
modifier: Modifier = Modifier,
) {
+ val localDensity = LocalDensity.current
val layoutWidth = remember { mutableStateOf(0) }
- Box(modifier.element(Shade.Elements.Scrim)) {
- Spacer(
- modifier =
- Modifier.element(Shade.Elements.ScrimBackground)
- .fillMaxSize()
- .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
- )
+ Box(
+ modifier =
+ modifier.element(Shade.Elements.Scrim).background(MaterialTheme.colorScheme.scrim),
+ )
+ Box {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
- modifier =
- Modifier.fillMaxSize()
- .clickable(onClick = { viewModel.onContentClicked() })
- .padding(
- start = Shade.Dimensions.HorizontalPadding,
- end = Shade.Dimensions.HorizontalPadding,
- bottom = 48.dp
- )
+ modifier = Modifier.fillMaxWidth().clickable(onClick = { viewModel.onContentClicked() })
) {
CollapsedShadeHeader(
viewModel = viewModel.shadeHeaderViewModel,
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
+ modifier = Modifier.padding(horizontal = Shade.Dimensions.HorizontalPadding)
)
- Spacer(modifier = Modifier.height(16.dp))
QuickSettings(
- modifier = Modifier.wrapContentHeight(),
+ modifier = Modifier.height(130.dp),
viewModel.qsSceneAdapter,
)
@@ -202,16 +200,15 @@ private fun SceneScope.ShadeScene(
},
mediaHost = mediaHost,
layoutWidth = layoutWidth.value,
- layoutHeight = with(LocalDensity.current) { mediaHeight.toPx() }.toInt(),
+ layoutHeight = with(localDensity) { mediaHeight.toPx() }.toInt(),
carouselController = mediaCarouselController,
)
}
Spacer(modifier = Modifier.height(16.dp))
- NotificationStack(
+ NotificationScrollingStack(
viewModel = viewModel.notifications,
- isScrimVisible = true,
- modifier = Modifier.weight(1f),
+ modifier = Modifier.fillMaxWidth().weight(1f),
)
}
}
diff --git a/packages/SystemUI/compose/features/tests/AndroidManifest.xml b/packages/SystemUI/compose/features/tests/AndroidManifest.xml
index 8fe9656c1879..fc337fb19f43 100644
--- a/packages/SystemUI/compose/features/tests/AndroidManifest.xml
+++ b/packages/SystemUI/compose/features/tests/AndroidManifest.xml
@@ -30,11 +30,6 @@
android:enabled="false"
tools:replace="android:authorities"
tools:node="remove" />
- <provider android:name="com.google.android.systemui.keyguard.KeyguardSliceProviderGoogle"
- android:authorities="com.android.systemui.test.keyguard.disabled"
- android:enabled="false"
- tools:replace="android:authorities"
- tools:node="remove" />
<provider android:name="com.android.systemui.keyguard.CustomizationProvider"
android:authorities="com.android.systemui.test.keyguard.quickaffordance.disabled"
android:enabled="false"
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
index 2c96d0e5402a..8e35988832dc 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt
@@ -36,24 +36,25 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
fun LargeTopAppBarNestedScrollConnection(
height: () -> Float,
onHeightChanged: (Float) -> Unit,
- heightRange: ClosedFloatingPointRange<Float>,
+ minHeight: () -> Float,
+ maxHeight: () -> Float,
): PriorityNestedScrollConnection {
- val minHeight = heightRange.start
- val maxHeight = heightRange.endInclusive
return PriorityNestedScrollConnection(
orientation = Orientation.Vertical,
// When swiping up, the LargeTopAppBar will shrink (to [minHeight]) and the content will
// expand. Then, you can then scroll down the content.
canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
- offsetAvailable < 0 && offsetBeforeStart == 0f && height() > minHeight
+ offsetAvailable < 0 && offsetBeforeStart == 0f && height() > minHeight()
},
// When swiping down, the content will scroll up until it reaches the top. Then, the
// LargeTopAppBar will expand until it reaches its [maxHeight].
- canStartPostScroll = { offsetAvailable, _ -> offsetAvailable > 0 && height() < maxHeight },
+ canStartPostScroll = { offsetAvailable, _ ->
+ offsetAvailable > 0 && height() < maxHeight()
+ },
canStartPostFling = { false },
canContinueScroll = {
val currentHeight = height()
- minHeight < currentHeight && currentHeight < maxHeight
+ minHeight() < currentHeight && currentHeight < maxHeight()
},
canScrollOnFling = true,
onStart = { /* do nothing */},
@@ -61,10 +62,10 @@ fun LargeTopAppBarNestedScrollConnection(
val currentHeight = height()
val amountConsumed =
if (offsetAvailable > 0) {
- val amountLeft = maxHeight - currentHeight
+ val amountLeft = maxHeight() - currentHeight
offsetAvailable.coerceAtMost(amountLeft)
} else {
- val amountLeft = minHeight - currentHeight
+ val amountLeft = minHeight() - currentHeight
offsetAvailable.coerceAtLeast(amountLeft)
}
onHeightChanged(currentHeight + amountConsumed)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
index e2974cddf1b9..ac7717b41a5a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnectionTest.kt
@@ -34,7 +34,8 @@ class LargeTopAppBarNestedScrollConnectionTest(testCase: TestCase) {
LargeTopAppBarNestedScrollConnection(
height = { height },
onHeightChanged = { height = it },
- heightRange = heightRange,
+ minHeight = { heightRange.start },
+ maxHeight = { heightRange.endInclusive },
)
private fun NestedScrollConnection.scroll(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index e89316997fb2..c4bcb536de78 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -35,6 +35,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository;
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -107,7 +108,7 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
mKeyguardMessageAreaControllerFactory, mLatencyTracker, mLiftToactivateListener,
mEmergencyButtonController, mFalsingCollector, featureFlags,
- mSelectedUserInteractor) {
+ mSelectedUserInteractor, new FakeKeyboardRepository()) {
@Override
public void onResume(int reason) {
super.onResume(reason);
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 78b854e39d13..c2efc05132ba 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -33,6 +33,7 @@ import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.classifier.FalsingCollectorFake
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
import com.android.systemui.res.R
import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED
@@ -141,7 +142,8 @@ class KeyguardPinViewControllerTest : SysuiTestCase() {
postureController,
featureFlags,
mSelectedUserInteractor,
- uiEventLogger
+ uiEventLogger,
+ FakeKeyboardRepository()
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index bc3ca1bd6c56..2a793ea70292 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -47,16 +47,20 @@ import com.android.systemui.classifier.FalsingA11yDelegate
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.SessionTracker
import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -66,6 +70,7 @@ import com.android.systemui.statusbar.policy.DevicePostureController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.statusbar.policy.UserSwitcherController
+import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.JavaAdapter
import com.android.systemui.util.mockito.any
@@ -156,7 +161,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
private lateinit var keyguardPasswordView: KeyguardPasswordView
private lateinit var testableResources: TestableResources
- private lateinit var sceneTestUtils: SceneTestUtils
+ private lateinit var kosmos: Kosmos
private lateinit var sceneInteractor: SceneInteractor
private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
private lateinit var deviceEntryInteractor: DeviceEntryInteractor
@@ -222,15 +227,15 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
mSelectedUserInteractor,
)
- sceneTestUtils = SceneTestUtils(this)
- sceneInteractor = sceneTestUtils.sceneInteractor()
+ kosmos = testKosmos()
+ sceneInteractor = kosmos.sceneInteractor
keyguardTransitionInteractor =
- KeyguardTransitionInteractorFactory.create(sceneTestUtils.testScope.backgroundScope)
+ KeyguardTransitionInteractorFactory.create(kosmos.testScope.backgroundScope)
.keyguardTransitionInteractor
sceneTransitionStateFlow =
MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
sceneInteractor.setTransitionState(sceneTransitionStateFlow)
- deviceEntryInteractor = sceneTestUtils.deviceEntryInteractor()
+ deviceEntryInteractor = kosmos.deviceEntryInteractor
mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
underTest =
@@ -249,7 +254,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
falsingManager,
userSwitcherController,
featureFlags,
- sceneTestUtils.fakeSceneContainerFlags,
+ kosmos.fakeSceneContainerFlags,
globalSettings,
sessionTracker,
Optional.of(sideFpsController),
@@ -259,7 +264,7 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
audioManager,
faceAuthInteractor,
mock(),
- { JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
+ { JavaAdapter(kosmos.testScope.backgroundScope) },
mSelectedUserInteractor,
deviceProvisionedController,
faceAuthAccessibilityDelegate,
@@ -786,8 +791,8 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() {
@Test
fun dismissesKeyguard_whenSceneChangesToGone() =
- sceneTestUtils.testScope.runTest {
- sceneTestUtils.fakeSceneContainerFlags.enabled = true
+ kosmos.testScope.runTest {
+ kosmos.fakeSceneContainerFlags.enabled = true
// Upon init, we have never dismisses the keyguard.
underTest.onInit()
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
index f7751753cc18..0959f1b2bcf6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt
@@ -16,7 +16,6 @@
package com.android.keyguard
-import android.telephony.PinResult
import android.telephony.TelephonyManager
import android.testing.TestableLooper
import android.view.LayoutInflater
@@ -28,9 +27,11 @@ import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
import com.android.systemui.res.R
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,7 +40,6 @@ import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
@@ -75,8 +75,7 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() {
`when`(messageAreaControllerFactory.create(Mockito.any(KeyguardMessageArea::class.java)))
.thenReturn(keyguardMessageAreaController)
`when`(telephonyManager.createForSubscriptionId(anyInt())).thenReturn(telephonyManager)
- `when`(telephonyManager.supplyIccLockPin(anyString()))
- .thenReturn(mock(PinResult::class.java))
+ `when`(telephonyManager.supplyIccLockPin(anyString())).thenReturn(mock())
simPinView =
LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null)
as KeyguardSimPinView
@@ -97,7 +96,8 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() {
falsingCollector,
emergencyButtonController,
fakeFeatureFlags,
- mSelectedUserInteractor
+ mSelectedUserInteractor,
+ FakeKeyboardRepository()
)
underTest.init()
underTest.onViewAttached()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
index 45a60199984b..1281e4409a83 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt
@@ -28,6 +28,7 @@ import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
import com.android.systemui.res.R
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.any
@@ -91,6 +92,7 @@ class KeyguardSimPukViewControllerTest : SysuiTestCase() {
emergencyButtonController,
fakeFeatureFlags,
mSelectedUserInteractor,
+ FakeKeyboardRepository()
)
underTest.init()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
index 27d1eb741bb0..c86c7470909b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
@@ -17,14 +17,15 @@
package com.android.systemui.accessibility.data.repository
import android.os.UserHandle
+import android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -51,6 +52,7 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
underTest =
ColorCorrectionRepositoryImpl(
testDispatcher,
+ scope.backgroundScope,
settings,
)
}
@@ -58,83 +60,78 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
@Test
fun isEnabled_initiallyGetsSettingsValue() =
scope.runTest {
+ val actualValue by collectLastValue(underTest.isEnabled(testUser1))
+
settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- 1,
+ SETTING_NAME,
+ ENABLED,
testUser1.identifier
)
-
- underTest =
- ColorCorrectionRepositoryImpl(
- testDispatcher,
- settings,
- )
-
- underTest.isEnabled(testUser1).launchIn(backgroundScope)
runCurrent()
- val actualValue: Boolean = underTest.isEnabled(testUser1).first()
Truth.assertThat(actualValue).isTrue()
}
@Test
fun isEnabled_settingUpdated_valueUpdated() =
scope.runTest {
- underTest.isEnabled(testUser1).launchIn(backgroundScope)
+ val flowValues: List<Boolean> by collectValues(underTest.isEnabled(testUser1))
settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- ColorCorrectionRepositoryImpl.DISABLED,
+ SETTING_NAME,
+ DISABLED,
testUser1.identifier
)
runCurrent()
- Truth.assertThat(underTest.isEnabled(testUser1).first()).isFalse()
settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- ColorCorrectionRepositoryImpl.ENABLED,
+ SETTING_NAME,
+ ENABLED,
testUser1.identifier
)
runCurrent()
- Truth.assertThat(underTest.isEnabled(testUser1).first()).isTrue()
settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- ColorCorrectionRepositoryImpl.DISABLED,
+ SETTING_NAME,
+ DISABLED,
testUser1.identifier
)
runCurrent()
- Truth.assertThat(underTest.isEnabled(testUser1).first()).isFalse()
+
+ Truth.assertThat(flowValues.size).isEqualTo(3)
+ Truth.assertThat(flowValues).containsExactly(false, true, false).inOrder()
}
@Test
fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() =
scope.runTest {
- underTest.isEnabled(testUser1).launchIn(backgroundScope)
+ val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1))
+ val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2))
+
settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- ColorCorrectionRepositoryImpl.DISABLED,
+ SETTING_NAME,
+ DISABLED,
testUser1.identifier
)
- underTest.isEnabled(testUser2).launchIn(backgroundScope)
settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- ColorCorrectionRepositoryImpl.DISABLED,
+ SETTING_NAME,
+ DISABLED,
testUser2.identifier
)
-
runCurrent()
- Truth.assertThat(underTest.isEnabled(testUser1).first()).isFalse()
- Truth.assertThat(underTest.isEnabled(testUser2).first()).isFalse()
+
+ Truth.assertThat(lastValueUser1).isFalse()
+ Truth.assertThat(lastValueUser2).isFalse()
settings.putIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
- ColorCorrectionRepositoryImpl.ENABLED,
+ SETTING_NAME,
+ ENABLED,
testUser1.identifier
)
runCurrent()
- Truth.assertThat(underTest.isEnabled(testUser1).first()).isTrue()
- Truth.assertThat(underTest.isEnabled(testUser2).first()).isFalse()
+
+ Truth.assertThat(lastValueUser1).isTrue()
+ Truth.assertThat(lastValueUser2).isFalse()
}
@Test
@@ -146,10 +143,10 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
val actualValue =
settings.getIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
+ SETTING_NAME,
testUser1.identifier
)
- Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.ENABLED)
+ Truth.assertThat(actualValue).isEqualTo(ENABLED)
}
@Test
@@ -161,9 +158,15 @@ class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
val actualValue =
settings.getIntForUser(
- ColorCorrectionRepositoryImpl.SETTING_NAME,
+ SETTING_NAME,
testUser1.identifier
)
- Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.DISABLED)
+ Truth.assertThat(actualValue).isEqualTo(DISABLED)
}
+
+ companion object {
+ private const val SETTING_NAME = ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED
+ private const val DISABLED = 0
+ private const val ENABLED = 1
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
index 423e124bbc84..4853529229fe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorInversionRepositoryImplTest.kt
@@ -21,11 +21,11 @@ import android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -52,6 +52,7 @@ class ColorInversionRepositoryImplTest : SysuiTestCase() {
underTest =
ColorInversionRepositoryImpl(
testDispatcher,
+ scope.backgroundScope,
settings,
)
}
@@ -59,55 +60,47 @@ class ColorInversionRepositoryImplTest : SysuiTestCase() {
@Test
fun isEnabled_initiallyGetsSettingsValue() =
scope.runTest {
- settings.putIntForUser(SETTING_NAME, 1, testUser1.identifier)
+ val actualValue by collectLastValue(underTest.isEnabled(testUser1))
- underTest =
- ColorInversionRepositoryImpl(
- testDispatcher,
- settings,
- )
-
- underTest.isEnabled(testUser1).launchIn(backgroundScope)
+ settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
- val actualValue: Boolean = underTest.isEnabled(testUser1).first()
assertThat(actualValue).isTrue()
}
@Test
fun isEnabled_settingUpdated_valueUpdated() =
scope.runTest {
- underTest.isEnabled(testUser1).launchIn(backgroundScope)
+ val flowValues: List<Boolean> by
+ collectValues(underTest.isEnabled(testUser1))
settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
runCurrent()
- assertThat(underTest.isEnabled(testUser1).first()).isFalse()
-
settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
- assertThat(underTest.isEnabled(testUser1).first()).isTrue()
-
settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
runCurrent()
- assertThat(underTest.isEnabled(testUser1).first()).isFalse()
+
+ assertThat(flowValues.size).isEqualTo(3)
+ assertThat(flowValues).containsExactly(false, true, false).inOrder()
}
@Test
fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() =
scope.runTest {
- underTest.isEnabled(testUser1).launchIn(backgroundScope)
+ val lastValueUser1 by collectLastValue(underTest.isEnabled(testUser1))
+ val lastValueUser2 by collectLastValue(underTest.isEnabled(testUser2))
+
settings.putIntForUser(SETTING_NAME, DISABLED, testUser1.identifier)
- underTest.isEnabled(testUser2).launchIn(backgroundScope)
settings.putIntForUser(SETTING_NAME, DISABLED, testUser2.identifier)
-
runCurrent()
- assertThat(underTest.isEnabled(testUser1).first()).isFalse()
- assertThat(underTest.isEnabled(testUser2).first()).isFalse()
+ assertThat(lastValueUser1).isFalse()
+ assertThat(lastValueUser2).isFalse()
settings.putIntForUser(SETTING_NAME, ENABLED, testUser1.identifier)
runCurrent()
- assertThat(underTest.isEnabled(testUser1).first()).isTrue()
- assertThat(underTest.isEnabled(testUser2).first()).isFalse()
+ assertThat(lastValueUser1).isTrue()
+ assertThat(lastValueUser2).isFalse()
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index b4d4e1f51251..caf92199737c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -29,10 +29,13 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.flag.sceneContainerFlags
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
@@ -59,8 +62,8 @@ class AuthenticationRepositoryTest : SysuiTestCase() {
@Mock private lateinit var tableLogger: TableLogBuffer
@Mock private lateinit var devicePolicyManager: DevicePolicyManager
- private val testUtils = SceneTestUtils(this)
- private val testScope = testUtils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private val clock = FakeSystemClock()
private val userRepository = FakeUserRepository()
private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
@@ -82,8 +85,8 @@ class AuthenticationRepositoryTest : SysuiTestCase() {
underTest =
AuthenticationRepositoryImpl(
applicationScope = testScope.backgroundScope,
- backgroundDispatcher = testUtils.testDispatcher,
- flags = testUtils.fakeSceneContainerFlags,
+ backgroundDispatcher = kosmos.testDispatcher,
+ flags = kosmos.sceneContainerFlags,
clock = clock,
getSecurityMode = getSecurityMode,
userRepository = userRepository,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 10c16bd2f3ed..cb8cebf80767 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -22,6 +22,7 @@ import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.None
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
@@ -29,7 +30,8 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,9 +47,9 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class AuthenticationInteractorTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val underTest = utils.authenticationInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val underTest = kosmos.authenticationInteractor
private val onAuthenticationResult by
testScope.collectLastValue(underTest.onAuthenticationResult)
@@ -62,7 +64,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
assertThat(authMethod).isEqualTo(Pin)
assertThat(underTest.getAuthenticationMethod()).isEqualTo(Pin)
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertThat(authMethod).isEqualTo(Password)
assertThat(underTest.getAuthenticationMethod()).isEqualTo(Password)
@@ -74,7 +76,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
val authMethod by collectLastValue(underTest.authenticationMethod)
runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(None)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(None)
assertThat(authMethod).isEqualTo(None)
assertThat(underTest.getAuthenticationMethod()).isEqualTo(None)
@@ -83,7 +85,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
@Test
fun authenticate_withCorrectPin_succeeds() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
}
@@ -91,7 +93,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
@Test
fun authenticate_withIncorrectPin_fails() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertFailed(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4)))
}
@@ -99,7 +101,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
@Test(expected = IllegalArgumentException::class)
fun authenticate_withEmptyPin_throwsException() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
underTest.authenticate(listOf())
}
@@ -107,7 +109,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
fun authenticate_withCorrectMaxLengthPin_succeeds() =
testScope.runTest {
val correctMaxLengthPin = List(16) { 9 }
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
overrideCredential(correctMaxLengthPin)
}
@@ -124,7 +126,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
// If the policy changes, there is work to do in SysUI.
assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertFailed(underTest.authenticate(List(17) { 9 }))
}
@@ -132,7 +134,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
@Test
fun authenticate_withCorrectPassword_succeeds() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertSucceeded(underTest.authenticate("password".toList()))
}
@@ -140,7 +142,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
@Test
fun authenticate_withIncorrectPassword_fails() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertFailed(underTest.authenticate("alohomora".toList()))
}
@@ -148,7 +150,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
@Test
fun authenticate_withCorrectPattern_succeeds() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Pattern)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern)
assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.PATTERN))
}
@@ -156,7 +158,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
@Test
fun authenticate_withIncorrectPattern_fails() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Pattern)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern)
val wrongPattern =
listOf(
AuthenticationPatternCoordinate(x = 2, y = 0),
@@ -172,7 +174,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
@@ -182,14 +184,14 @@ class AuthenticationInteractorTest : SysuiTestCase() {
assertSkipped(underTest.authenticate(shorterPin, tryAutoConfirm = true))
assertThat(underTest.lockoutEndTimestamp).isNull()
- assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
+ assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(0)
}
@Test
fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalse() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
@@ -207,7 +209,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalse() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
@@ -225,7 +227,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrue() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
}
@@ -241,7 +243,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
reportLockoutStarted(42)
@@ -258,7 +260,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
@Test
fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNull() =
testScope.runTest {
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(false)
}
@@ -271,7 +273,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
@Test
fun tryAutoConfirm_withoutCorrectPassword_returnsNull() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertSkipped(underTest.authenticate("password".toList(), tryAutoConfirm = true))
}
@@ -280,7 +282,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
fun isAutoConfirmEnabled_featureDisabled_returnsFalse() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(false)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(false)
assertThat(isAutoConfirmEnabled).isFalse()
}
@@ -289,7 +291,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
fun isAutoConfirmEnabled_featureEnabled_returnsTrue() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
assertThat(isAutoConfirmEnabled).isTrue()
}
@@ -298,7 +300,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
fun isAutoConfirmEnabled_featureEnabledButDisabledByLockout() =
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
// The feature is enabled.
assertThat(isAutoConfirmEnabled).isTrue()
@@ -308,7 +310,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
assertFailed(underTest.authenticate(listOf(5, 6, 7))) // Wrong PIN
}
assertThat(underTest.lockoutEndTimestamp).isNotNull()
- assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
+ assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(1)
// Lockout disabled auto-confirm.
assertThat(isAutoConfirmEnabled).isFalse()
@@ -336,7 +338,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
val failedAuthenticationAttempts by
collectLastValue(underTest.failedAuthenticationAttempts)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
assertSucceeded(underTest.authenticate(correctPin))
@@ -366,7 +368,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
@Test
fun lockoutEndTimestamp() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
underTest.authenticate(correctPin)
@@ -384,7 +386,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
val expectedLockoutEndTimestamp =
testScope.currentTime + FakeAuthenticationRepository.LOCKOUT_DURATION_MS
assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp)
- assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
+ assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(1)
// Correct PIN, but locked out, so doesn't attempt it:
assertSkipped(underTest.authenticate(correctPin), assertNoResultEvents = false)
@@ -409,7 +411,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
fun upcomingWipe() =
testScope.runTest {
val upcomingWipe by collectLastValue(underTest.upcomingWipe)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
val wrongPin = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 }
@@ -418,7 +420,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
var expectedFailedAttempts = 0
var remainingFailedAttempts =
- utils.authenticationRepository.getMaxFailedUnlockAttemptsForWipe()
+ kosmos.fakeAuthenticationRepository.getMaxFailedUnlockAttemptsForWipe()
assertThat(remainingFailedAttempts)
.isGreaterThan(LockPatternUtils.FAILED_ATTEMPTS_BEFORE_WIPE_GRACE)
@@ -458,7 +460,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
fun hintedPinLength_withoutAutoConfirm_isNull() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(false)
}
@@ -470,11 +472,13 @@ class AuthenticationInteractorTest : SysuiTestCase() {
fun hintedPinLength_withAutoConfirmPinTooShort_isNull() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
overrideCredential(
buildList {
- repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
+ repeat(kosmos.fakeAuthenticationRepository.hintedPinLength - 1) {
+ add(it + 1)
+ }
}
)
setAutoConfirmFeatureEnabled(true)
@@ -487,28 +491,31 @@ class AuthenticationInteractorTest : SysuiTestCase() {
fun hintedPinLength_withAutoConfirmPinAtRightLength_isSameLength() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
setAutoConfirmFeatureEnabled(true)
overrideCredential(
buildList {
- repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) }
+ repeat(kosmos.fakeAuthenticationRepository.hintedPinLength) { add(it + 1) }
}
)
}
- assertThat(hintedPinLength).isEqualTo(utils.authenticationRepository.hintedPinLength)
+ assertThat(hintedPinLength)
+ .isEqualTo(kosmos.fakeAuthenticationRepository.hintedPinLength)
}
@Test
fun hintedPinLength_withAutoConfirmPinTooLong_isNull() =
testScope.runTest {
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
- utils.authenticationRepository.apply {
+ kosmos.fakeAuthenticationRepository.apply {
setAuthenticationMethod(Pin)
overrideCredential(
buildList {
- repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
+ repeat(kosmos.fakeAuthenticationRepository.hintedPinLength + 1) {
+ add(it + 1)
+ }
}
)
setAutoConfirmFeatureEnabled(true)
@@ -520,10 +527,10 @@ class AuthenticationInteractorTest : SysuiTestCase() {
@Test
fun authenticate_withTooShortPassword() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
val tooShortPassword = buildList {
- repeat(utils.authenticationRepository.minPasswordLength - 1) { time ->
+ repeat(kosmos.fakeAuthenticationRepository.minPasswordLength - 1) { time ->
add("$time")
}
}
@@ -534,7 +541,7 @@ class AuthenticationInteractorTest : SysuiTestCase() {
assertThat(authenticationResult).isEqualTo(AuthenticationResult.SUCCEEDED)
assertThat(onAuthenticationResult).isTrue()
assertThat(underTest.lockoutEndTimestamp).isNull()
- assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
+ assertThat(kosmos.fakeAuthenticationRepository.lockoutStartedReportCount).isEqualTo(0)
assertThat(failedAuthenticationAttempts).isEqualTo(0)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 4a39799fd64f..72e884e9e5d6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics
+import android.graphics.Bitmap
import android.hardware.biometrics.BiometricManager.Authenticators
import android.hardware.biometrics.ComponentInfoInternal
import android.hardware.biometrics.PromptContentView
@@ -117,6 +118,8 @@ internal fun Collection<SensorPropertiesInternal?>.extractAuthenticatorTypes():
}
internal fun promptInfo(
+ logoRes: Int = -1,
+ logoBitmap: Bitmap? = null,
title: String = "title",
subtitle: String = "sub",
description: String = "desc",
@@ -127,6 +130,8 @@ internal fun promptInfo(
negativeButton: String = "neg",
): PromptInfo {
val info = PromptInfo()
+ info.logoRes = logoRes
+ info.logoBitmap = logoBitmap
info.title = title
info.subtitle = subtitle
info.description = description
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
index c2117ae5bda4..a67b0931f171 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/data/repository/EmergencyServicesRepositoryImplTest.kt
@@ -20,8 +20,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestScope
@@ -36,8 +39,8 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class EmergencyServicesRepositoryImplTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var underTest: EmergencyServicesRepository
@@ -52,7 +55,7 @@ class EmergencyServicesRepositoryImplTest : SysuiTestCase() {
EmergencyServicesRepository(
resources = context.resources,
applicationScope = testScope.backgroundScope,
- configurationRepository = utils.configurationRepository,
+ configurationRepository = kosmos.configurationRepository,
)
}
@@ -71,7 +74,7 @@ class EmergencyServicesRepositoryImplTest : SysuiTestCase() {
private fun TestScope.setEmergencyCallWhileSimLocked(isEnabled: Boolean) {
overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, isEnabled)
- utils.configurationRepository.onConfigurationChange()
+ kosmos.fakeConfigurationRepository.onConfigurationChange()
runCurrent()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
index 63581b3f7070..741cde82354a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorTest.kt
@@ -25,11 +25,18 @@ import com.android.internal.logging.fakeMetricsLogger
import com.android.internal.logging.nano.MetricsProto
import com.android.internal.util.emergencyAffordanceManager
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.common.ui.data.repository.fakeConfigurationRepository
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags.REFACTOR_GETCURRENTUSER
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
+import com.android.systemui.testKosmos
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.mockito.whenever
import com.android.telecom.telecomManager
@@ -54,11 +61,11 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() {
@Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
@Mock private lateinit var telecomManager: TelecomManager
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val metricsLogger = utils.kosmos.fakeMetricsLogger
- private val activityTaskManager = utils.kosmos.activityTaskManager
- private val emergencyAffordanceManager = utils.kosmos.emergencyAffordanceManager
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val metricsLogger = kosmos.fakeMetricsLogger
+ private val activityTaskManager = kosmos.activityTaskManager
+ private val emergencyAffordanceManager = kosmos.emergencyAffordanceManager
private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
@@ -68,9 +75,9 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- utils.fakeSceneContainerFlags.enabled = true
+ kosmos.fakeSceneContainerFlags.enabled = true
- mobileConnectionsRepository = utils.mobileConnectionsRepository
+ mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
overrideResource(R.string.lockscreen_emergency_call, MESSAGE_EMERGENCY_CALL)
overrideResource(R.string.lockscreen_return_to_call, MESSAGE_RETURN_TO_CALL)
@@ -83,18 +90,18 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() {
.thenReturn(needsEmergencyAffordance)
whenever(telecomManager.isInCall).thenReturn(false)
- utils.fakeFeatureFlags.set(REFACTOR_GETCURRENTUSER, true)
+ kosmos.fakeFeatureFlagsClassic.set(REFACTOR_GETCURRENTUSER, true)
- utils.telephonyRepository.setHasTelephonyRadio(true)
+ kosmos.fakeTelephonyRepository.setHasTelephonyRadio(true)
- utils.kosmos.telecomManager = telecomManager
+ kosmos.telecomManager = telecomManager
}
@Test
fun noTelephonyRadio_noButton() =
testScope.runTest {
- utils.telephonyRepository.setHasTelephonyRadio(false)
- val underTest = utils.bouncerActionButtonInteractor()
+ kosmos.fakeTelephonyRepository.setHasTelephonyRadio(false)
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
assertThat(actionButton).isNull()
}
@@ -102,8 +109,8 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() {
@Test
fun noTelecomManager_noButton() =
testScope.runTest {
- utils.kosmos.telecomManager = null
- val underTest = utils.bouncerActionButtonInteractor()
+ kosmos.telecomManager = null
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
assertThat(actionButton).isNull()
}
@@ -111,9 +118,9 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() {
@Test
fun duringCall_returnToCallButton() =
testScope.runTest {
- val underTest = utils.bouncerActionButtonInteractor()
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
- utils.telephonyRepository.setIsInCall(true)
+ kosmos.fakeTelephonyRepository.setIsInCall(true)
assertThat(actionButton).isNotNull()
assertThat(actionButton?.label).isEqualTo(MESSAGE_RETURN_TO_CALL)
@@ -133,11 +140,13 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() {
@Test
fun noCall_secureAuthMethod_emergencyCallButton() =
testScope.runTest {
- val underTest = utils.bouncerActionButtonInteractor()
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
mobileConnectionsRepository.isAnySimSecure.value = false
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.telephonyRepository.setIsInCall(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeTelephonyRepository.setIsInCall(false)
assertThat(actionButton).isNotNull()
assertThat(actionButton?.label).isEqualTo(MESSAGE_EMERGENCY_CALL)
@@ -163,11 +172,13 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() {
@Test
fun noCall_insecureAuthMethodButSecureSim_emergencyCallButton() =
testScope.runTest {
- val underTest = utils.bouncerActionButtonInteractor()
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
mobileConnectionsRepository.isAnySimSecure.value = true
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.telephonyRepository.setIsInCall(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ kosmos.fakeTelephonyRepository.setIsInCall(false)
runCurrent()
assertThat(actionButton).isNotNull()
@@ -179,11 +190,13 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() {
@Test
fun noCall_insecure_noButton() =
testScope.runTest {
- val underTest = utils.bouncerActionButtonInteractor()
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
mobileConnectionsRepository.isAnySimSecure.value = false
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.telephonyRepository.setIsInCall(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ kosmos.fakeTelephonyRepository.setIsInCall(false)
assertThat(actionButton).isNull()
}
@@ -191,13 +204,15 @@ class BouncerActionButtonInteractorTest : SysuiTestCase() {
@Test
fun noCall_simSecureButEmergencyNotSupported_noButton() =
testScope.runTest {
- val underTest = utils.bouncerActionButtonInteractor()
+ val underTest = kosmos.bouncerActionButtonInteractor
val actionButton by collectLastValue(underTest.actionButton)
mobileConnectionsRepository.isAnySimSecure.value = true
overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, false)
- utils.configurationRepository.onConfigurationChange()
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.telephonyRepository.setIsInCall(false)
+ kosmos.fakeConfigurationRepository.onConfigurationChange()
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ kosmos.fakeTelephonyRepository.setIsInCall(false)
runCurrent()
assertThat(actionButton).isNull()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 4b6199b55b58..707777b9f728 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -20,15 +20,20 @@ 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.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,9 +50,9 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
class BouncerInteractorTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this).apply { fakeSceneContainerFlags.enabled = true }
- private val testScope = utils.testScope
- private val authenticationInteractor = utils.authenticationInteractor()
+ private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ private val testScope = kosmos.testScope
+ private val authenticationInteractor = kosmos.authenticationInteractor
private lateinit var underTest: BouncerInteractor
@@ -62,7 +67,7 @@ class BouncerInteractorTest : SysuiTestCase() {
overrideResource(R.string.kg_wrong_password, MESSAGE_WRONG_PASSWORD)
overrideResource(R.string.kg_wrong_pattern, MESSAGE_WRONG_PATTERN)
- underTest = utils.bouncerInteractor()
+ underTest = kosmos.bouncerInteractor
}
@Test
@@ -70,7 +75,9 @@ class BouncerInteractorTest : SysuiTestCase() {
testScope.runTest {
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
runCurrent()
underTest.clearMessage()
assertThat(message).isNull()
@@ -94,7 +101,9 @@ class BouncerInteractorTest : SysuiTestCase() {
@Test
fun pinAuthMethod_sim_skipsAuthentication() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Sim
+ )
runCurrent()
// We rely on TelephonyManager to authenticate the sim card.
@@ -109,9 +118,11 @@ class BouncerInteractorTest : SysuiTestCase() {
testScope.runTest {
val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
runCurrent()
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
assertThat(isAutoConfirmEnabled).isTrue()
// Incomplete input.
@@ -137,7 +148,9 @@ class BouncerInteractorTest : SysuiTestCase() {
testScope.runTest {
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
runCurrent()
// Incomplete input.
@@ -160,7 +173,7 @@ class BouncerInteractorTest : SysuiTestCase() {
fun passwordAuthMethod() =
testScope.runTest {
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
runCurrent()
@@ -180,7 +193,8 @@ class BouncerInteractorTest : SysuiTestCase() {
assertThat(
underTest.authenticate(
buildList {
- repeat(utils.authenticationRepository.minPasswordLength - 1) { time ->
+ repeat(kosmos.fakeAuthenticationRepository.minPasswordLength - 1) { time
+ ->
add("$time")
}
}
@@ -198,7 +212,7 @@ class BouncerInteractorTest : SysuiTestCase() {
fun patternAuthMethod() =
testScope.runTest {
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern
)
runCurrent()
@@ -214,7 +228,8 @@ class BouncerInteractorTest : SysuiTestCase() {
AuthenticationPatternCoordinate(0, 1),
)
assertThat(wrongPattern).isNotEqualTo(FakeAuthenticationRepository.PATTERN)
- assertThat(wrongPattern.size).isAtLeast(utils.authenticationRepository.minPatternLength)
+ assertThat(wrongPattern.size)
+ .isAtLeast(kosmos.fakeAuthenticationRepository.minPatternLength)
assertThat(underTest.authenticate(wrongPattern)).isEqualTo(AuthenticationResult.FAILED)
assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN)
@@ -225,7 +240,7 @@ class BouncerInteractorTest : SysuiTestCase() {
val tooShortPattern =
FakeAuthenticationRepository.PATTERN.subList(
0,
- utils.authenticationRepository.minPatternLength - 1
+ kosmos.fakeAuthenticationRepository.minPatternLength - 1
)
assertThat(underTest.authenticate(tooShortPattern))
.isEqualTo(AuthenticationResult.SKIPPED)
@@ -245,7 +260,9 @@ class BouncerInteractorTest : SysuiTestCase() {
val lockoutStartedEvents by collectValues(underTest.onLockoutStarted)
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
assertThat(lockoutStartedEvents).isEmpty()
// Try the wrong PIN repeatedly, until lockout is triggered:
@@ -291,17 +308,17 @@ class BouncerInteractorTest : SysuiTestCase() {
@Test
fun intentionalUserInputEvent_registersTouchEvent() =
testScope.runTest {
- assertThat(utils.powerRepository.userTouchRegistered).isFalse()
+ assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse()
underTest.onIntentionalUserInput()
- assertThat(utils.powerRepository.userTouchRegistered).isTrue()
+ assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
}
@Test
fun intentionalUserInputEvent_notifiesFaceAuthInteractor() =
testScope.runTest {
val isFaceAuthRunning by
- collectLastValue(utils.kosmos.fakeDeviceEntryFaceAuthRepository.isAuthRunning)
- utils.kosmos.deviceEntryFaceAuthInteractor.onDeviceLifted()
+ collectLastValue(kosmos.fakeDeviceEntryFaceAuthRepository.isAuthRunning)
+ kosmos.deviceEntryFaceAuthInteractor.onDeviceLifted()
runCurrent()
assertThat(isFaceAuthRunning).isTrue()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
index 8c53c0e3f267..09fdd11a99dd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/SimBouncerInteractorTest.kt
@@ -28,8 +28,11 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.FakeSimBouncerRepository
import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor.Companion.INVALID_SUBSCRIPTION_ID
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.mobileConnectionsRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -54,10 +57,10 @@ class SimBouncerInteractorTest : SysuiTestCase() {
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock lateinit var euiccManager: EuiccManager
- private val utils = SceneTestUtils(this)
+ private val kosmos = testKosmos()
private val bouncerSimRepository = FakeSimBouncerRepository()
private val resources: Resources = context.resources
- private val testScope = utils.testScope
+ private val testScope = kosmos.testScope
private lateinit var underTest: SimBouncerInteractor
@@ -68,13 +71,13 @@ class SimBouncerInteractorTest : SysuiTestCase() {
SimBouncerInteractor(
context,
testScope.backgroundScope,
- utils.testDispatcher,
+ kosmos.testDispatcher,
bouncerSimRepository,
telephonyManager,
resources,
keyguardUpdateMonitor,
euiccManager,
- utils.mobileConnectionsRepository,
+ kosmos.mobileConnectionsRepository,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 3043a710276b..27b84b2ffabc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -20,9 +20,13 @@ 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.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
@@ -33,16 +37,16 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class AuthMethodBouncerViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val bouncerInteractor = utils.bouncerInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val bouncerInteractor = kosmos.bouncerInteractor
private val underTest =
PinBouncerViewModel(
applicationContext = context,
viewModelScope = testScope.backgroundScope,
interactor = bouncerInteractor,
isInputEnabled = MutableStateFlow(true),
- simBouncerInteractor = utils.simBouncerInteractor,
+ simBouncerInteractor = kosmos.simBouncerInteractor,
authenticationMethod = AuthenticationMethodModel.Pin,
)
@@ -50,7 +54,9 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() {
fun animateFailure() =
testScope.runTest {
val animateFailure by collectLastValue(underTest.animateFailure)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
assertThat(animateFailure).isFalse()
// Wrong PIN:
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 4b1f9fee48bb..cfe8c5d52c18 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -20,15 +20,21 @@ 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.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.None
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Sim
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlin.time.Duration.Companion.seconds
@@ -50,16 +56,16 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class BouncerViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val authenticationInteractor = utils.authenticationInteractor()
- private val bouncerInteractor = utils.bouncerInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val authenticationInteractor = kosmos.authenticationInteractor
+ private val bouncerInteractor = kosmos.bouncerInteractor
private lateinit var underTest: BouncerViewModel
@Before
fun setUp() {
- utils.fakeSceneContainerFlags.enabled = true
- underTest = utils.bouncerViewModel()
+ kosmos.fakeSceneContainerFlags.enabled = true
+ underTest = kosmos.bouncerViewModel
}
@Test
@@ -68,7 +74,7 @@ class BouncerViewModelTest : SysuiTestCase() {
var authMethodViewModel: AuthMethodBouncerViewModel? = null
authMethodsToTest().forEach { authMethod ->
- utils.authenticationRepository.setAuthenticationMethod(authMethod)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
val job =
underTest.authMethodViewModel.onEach { authMethodViewModel = it }.launchIn(this)
runCurrent()
@@ -98,13 +104,13 @@ class BouncerViewModelTest : SysuiTestCase() {
// First pass, populate our "seen" map:
authMethodsToTest().forEach { authMethod ->
- utils.authenticationRepository.setAuthenticationMethod(authMethod)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
authMethodViewModel?.let { seen[authMethod] = it }
}
// Second pass, assert same instances are not reused:
authMethodsToTest().forEach { authMethod ->
- utils.authenticationRepository.setAuthenticationMethod(authMethod)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
authMethodViewModel?.let {
assertThat(it.authenticationMethod).isEqualTo(authMethod)
assertThat(it).isNotSameInstanceAs(seen[authMethod])
@@ -116,11 +122,11 @@ class BouncerViewModelTest : SysuiTestCase() {
fun authMethodUnchanged_reusesInstances() =
testScope.runTest {
authMethodsToTest().forEach { authMethod ->
- utils.authenticationRepository.setAuthenticationMethod(authMethod)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
val firstInstance: AuthMethodBouncerViewModel? =
collectLastValue(underTest.authMethodViewModel).invoke()
- utils.authenticationRepository.setAuthenticationMethod(authMethod)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
val secondInstance: AuthMethodBouncerViewModel? =
collectLastValue(underTest.authMethodViewModel).invoke()
@@ -139,7 +145,7 @@ class BouncerViewModelTest : SysuiTestCase() {
fun message() =
testScope.runTest {
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(message?.isUpdateAnimated).isTrue()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
@@ -157,8 +163,8 @@ class BouncerViewModelTest : SysuiTestCase() {
testScope.runTest {
val authMethodViewModel by collectLastValue(underTest.authMethodViewModel)
val message by collectLastValue(underTest.message)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
- assertThat(utils.authenticationRepository.lockoutEndTimestamp).isNull()
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
+ assertThat(kosmos.fakeAuthenticationRepository.lockoutEndTimestamp).isNull()
assertThat(authMethodViewModel?.lockoutMessageId).isNotNull()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times ->
@@ -192,7 +198,7 @@ class BouncerViewModelTest : SysuiTestCase() {
authViewModel?.isInputEnabled ?: emptyFlow()
}
)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(isInputEnabled).isTrue()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
@@ -210,7 +216,7 @@ class BouncerViewModelTest : SysuiTestCase() {
testScope.runTest {
val authMethodViewModel by collectLastValue(underTest.authMethodViewModel)
val dialogViewModel by collectLastValue(underTest.dialogViewModel)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(authMethodViewModel?.lockoutMessageId).isNotNull()
repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
@@ -228,17 +234,17 @@ class BouncerViewModelTest : SysuiTestCase() {
fun isSideBySideSupported() =
testScope.runTest {
val isSideBySideSupported by collectLastValue(underTest.isSideBySideSupported)
- utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(isSideBySideSupported).isTrue()
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertThat(isSideBySideSupported).isTrue()
- utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(isSideBySideSupported).isTrue()
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertThat(isSideBySideSupported).isFalse()
}
@@ -246,12 +252,12 @@ class BouncerViewModelTest : SysuiTestCase() {
fun isFoldSplitRequired() =
testScope.runTest {
val isFoldSplitRequired by collectLastValue(underTest.isFoldSplitRequired)
- utils.authenticationRepository.setAuthenticationMethod(Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
assertThat(isFoldSplitRequired).isTrue()
- utils.authenticationRepository.setAuthenticationMethod(Password)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Password)
assertThat(isFoldSplitRequired).isFalse()
- utils.authenticationRepository.setAuthenticationMethod(Pattern)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pattern)
assertThat(isFoldSplitRequired).isTrue()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 5c5632f6aa7b..b3b6457b46e7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -19,13 +19,19 @@ package com.android.systemui.bouncer.ui.viewmodel
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.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -43,12 +49,12 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class PasswordBouncerViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val authenticationInteractor = utils.authenticationInteractor()
- private val sceneInteractor = utils.sceneInteractor()
- private val bouncerInteractor = utils.bouncerInteractor()
- private val bouncerViewModel = utils.bouncerViewModel()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val authenticationInteractor = kosmos.authenticationInteractor
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val bouncerInteractor = kosmos.bouncerInteractor
+ private val bouncerViewModel = kosmos.bouncerViewModel
private val isInputEnabled = MutableStateFlow(true)
private val underTest =
@@ -140,10 +146,10 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
testScope.runTest {
val message by collectLastValue(bouncerViewModel.message)
val password by collectLastValue(underTest.password)
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(SceneKey.Bouncer)
// No input entered.
@@ -309,8 +315,10 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
}
private fun TestScope.lockDeviceAndOpenPasswordBouncer() {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Password)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Password
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(SceneKey.Bouncer)
}
@@ -320,13 +328,13 @@ class PasswordBouncerViewModelTest : SysuiTestCase() {
) {
if (isLockedOut) {
repeat(failedAttemptCount) {
- utils.authenticationRepository.reportAuthenticationAttempt(false)
+ kosmos.fakeAuthenticationRepository.reportAuthenticationAttempt(false)
}
- utils.authenticationRepository.reportLockoutStarted(
+ kosmos.fakeAuthenticationRepository.reportLockoutStarted(
30.seconds.inWholeMilliseconds.toInt()
)
} else {
- utils.authenticationRepository.reportAuthenticationAttempt(true)
+ kosmos.fakeAuthenticationRepository.reportAuthenticationAttempt(true)
}
isInputEnabled.value = !isLockedOut
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 9ee344a9527a..c2680bcc82a3 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
@@ -20,13 +20,20 @@ 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.data.repository.authenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,12 +51,12 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class PatternBouncerViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val authenticationInteractor = utils.authenticationInteractor()
- private val sceneInteractor = utils.sceneInteractor()
- private val bouncerInteractor = utils.bouncerInteractor()
- private val bouncerViewModel = utils.bouncerViewModel()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val authenticationInteractor = kosmos.authenticationInteractor
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val bouncerInteractor = kosmos.bouncerInteractor
+ private val bouncerViewModel = kosmos.bouncerViewModel
private val underTest =
PatternBouncerViewModel(
applicationContext = context,
@@ -305,7 +312,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
underTest.onDragStart()
CORRECT_PATTERN.subList(
0,
- utils.authenticationRepository.minPatternLength - 1,
+ kosmos.authenticationRepository.minPatternLength - 1,
)
.forEach { coordinate ->
underTest.onDrag(
@@ -374,8 +381,10 @@ class PatternBouncerViewModelTest : SysuiTestCase() {
}
private fun TestScope.lockDeviceAndOpenPatternBouncer() {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pattern)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pattern
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(SceneKey.Bouncer)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 75e372f29590..1d660d63710d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -20,12 +20,20 @@ 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.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -43,19 +51,19 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class PinBouncerViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val sceneInteractor = utils.sceneInteractor()
- private val authenticationInteractor = utils.authenticationInteractor()
- private val bouncerInteractor = utils.bouncerInteractor()
- private val bouncerViewModel = utils.bouncerViewModel()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val authenticationInteractor = kosmos.authenticationInteractor
+ private val bouncerInteractor = kosmos.bouncerInteractor
+ private val bouncerViewModel = kosmos.bouncerViewModel
private val underTest =
PinBouncerViewModel(
applicationContext = context,
viewModelScope = testScope.backgroundScope,
interactor = bouncerInteractor,
isInputEnabled = MutableStateFlow(true).asStateFlow(),
- simBouncerInteractor = utils.simBouncerInteractor,
+ simBouncerInteractor = kosmos.simBouncerInteractor,
authenticationMethod = AuthenticationMethodModel.Pin,
)
@@ -86,7 +94,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
viewModelScope = testScope.backgroundScope,
interactor = bouncerInteractor,
isInputEnabled = MutableStateFlow(true).asStateFlow(),
- simBouncerInteractor = utils.simBouncerInteractor,
+ simBouncerInteractor = kosmos.simBouncerInteractor,
authenticationMethod = AuthenticationMethodModel.Sim,
)
@@ -97,7 +105,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
fun onErrorDialogDismissed_clearsDialogMessage() =
testScope.runTest {
val dialogMessage by collectLastValue(underTest.errorDialogMessage)
- utils.simBouncerRepository.setSimVerificationErrorMessage("abc")
+ kosmos.fakeSimBouncerRepository.setSimVerificationErrorMessage("abc")
assertThat(dialogMessage).isEqualTo("abc")
underTest.onErrorDialogDismissed()
@@ -114,10 +122,10 @@ class PinBouncerViewModelTest : SysuiTestCase() {
viewModelScope = testScope.backgroundScope,
interactor = bouncerInteractor,
isInputEnabled = MutableStateFlow(true).asStateFlow(),
- simBouncerInteractor = utils.simBouncerInteractor,
+ simBouncerInteractor = kosmos.simBouncerInteractor,
authenticationMethod = AuthenticationMethodModel.Sim,
)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
assertThat(hintedPinLength).isNull()
@@ -254,7 +262,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
@Test
fun onAutoConfirm_whenCorrect() =
testScope.runTest {
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
lockDeviceAndOpenPinBouncer()
@@ -269,7 +277,7 @@ class PinBouncerViewModelTest : SysuiTestCase() {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
val message by collectLastValue(bouncerViewModel.message)
val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
lockDeviceAndOpenPinBouncer()
FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit ->
@@ -309,7 +317,9 @@ class PinBouncerViewModelTest : SysuiTestCase() {
testScope.runTest {
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
}
@@ -318,8 +328,10 @@ class PinBouncerViewModelTest : SysuiTestCase() {
fun backspaceButtonAppearance_withAutoConfirmButNoInput_isHidden() =
testScope.runTest {
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
assertThat(backspaceButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
}
@@ -328,8 +340,10 @@ class PinBouncerViewModelTest : SysuiTestCase() {
fun backspaceButtonAppearance_withAutoConfirmAndInput_isShownQuiet() =
testScope.runTest {
val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
underTest.onPinButtonClicked(1)
@@ -341,7 +355,9 @@ class PinBouncerViewModelTest : SysuiTestCase() {
testScope.runTest {
val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Shown)
}
@@ -350,8 +366,10 @@ class PinBouncerViewModelTest : SysuiTestCase() {
fun confirmButtonAppearance_withAutoConfirm_isHidden() =
testScope.runTest {
val confirmButtonAppearance by collectLastValue(underTest.confirmButtonAppearance)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
assertThat(confirmButtonAppearance).isEqualTo(ActionButtonAppearance.Hidden)
}
@@ -361,10 +379,10 @@ class PinBouncerViewModelTest : SysuiTestCase() {
testScope.runTest {
val isAnimationEnabled by collectLastValue(underTest.isDigitButtonAnimationEnabled)
- utils.authenticationRepository.setPinEnhancedPrivacyEnabled(true)
+ kosmos.fakeAuthenticationRepository.setPinEnhancedPrivacyEnabled(true)
assertThat(isAnimationEnabled).isFalse()
- utils.authenticationRepository.setPinEnhancedPrivacyEnabled(false)
+ kosmos.fakeAuthenticationRepository.setPinEnhancedPrivacyEnabled(false)
assertThat(isAnimationEnabled).isTrue()
}
@@ -382,8 +400,8 @@ class PinBouncerViewModelTest : SysuiTestCase() {
}
private fun TestScope.lockDeviceAndOpenPinBouncer() {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
switchToScene(SceneKey.Bouncer)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
new file mode 100644
index 000000000000..820bfbfdf0a2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.data.repository
+
+import android.content.SharedPreferences
+import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryImpl.Companion.FILE_NAME
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.util.FakeSharedPreferences
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalPrefsRepositoryImplTest : SysuiTestCase() {
+ private lateinit var underTest: CommunalPrefsRepositoryImpl
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var userRepository: FakeUserRepository
+ private lateinit var userFileManager: UserFileManager
+
+ @Before
+ fun setUp() {
+ userRepository = kosmos.fakeUserRepository
+ userRepository.setUserInfos(USER_INFOS)
+
+ userFileManager =
+ FakeUserFileManager(
+ mapOf(
+ USER_INFOS[0].id to FakeSharedPreferences(),
+ USER_INFOS[1].id to FakeSharedPreferences()
+ )
+ )
+ underTest =
+ CommunalPrefsRepositoryImpl(
+ testScope.backgroundScope,
+ kosmos.testDispatcher,
+ userRepository,
+ userFileManager,
+ )
+ }
+
+ @Test
+ fun isCtaDismissedValue_byDefault_isFalse() =
+ testScope.runTest {
+ val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
+ assertThat(isCtaDismissed).isFalse()
+ }
+
+ @Test
+ fun isCtaDismissedValue_onSet_isTrue() =
+ testScope.runTest {
+ val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
+
+ underTest.setCtaDismissedForCurrentUser()
+ assertThat(isCtaDismissed).isTrue()
+ }
+
+ @Test
+ fun isCtaDismissedValue_whenSwitchUser() =
+ testScope.runTest {
+ val isCtaDismissed by collectLastValue(underTest.isCtaDismissed)
+ underTest.setCtaDismissedForCurrentUser()
+
+ // dismissed true for primary user
+ assertThat(isCtaDismissed).isTrue()
+
+ // switch to secondary user
+ userRepository.setSelectedUserInfo(USER_INFOS[1])
+
+ // dismissed is false for secondary user
+ assertThat(isCtaDismissed).isFalse()
+
+ // switch back to primary user
+ userRepository.setSelectedUserInfo(USER_INFOS[0])
+
+ // dismissed is true for primary user
+ assertThat(isCtaDismissed).isTrue()
+ }
+
+ private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
+ UserFileManager {
+ override fun getFile(fileName: String, userId: Int): File {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getSharedPreferences(
+ fileName: String,
+ mode: Int,
+ userId: Int
+ ): SharedPreferences {
+ if (fileName != FILE_NAME) {
+ throw IllegalArgumentException("Preference files must be $FILE_NAME")
+ }
+ return sharedPrefs.getValue(userId)
+ }
+ }
+
+ companion object {
+ val USER_INFOS =
+ listOf(
+ UserInfo(/* id= */ 0, "zero", /* flags= */ 0),
+ UserInfo(/* id= */ 1, "secondary", /* flags= */ 0),
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
index 65176e1c5c0d..81d5344ed264 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt
@@ -24,11 +24,12 @@ import com.android.systemui.communal.shared.model.ObservableCommunalTransitionSt
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.scene.data.repository.SceneContainerRepository
+import com.android.systemui.scene.data.repository.sceneContainerRepository
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -51,8 +52,8 @@ class CommunalRepositoryImplTest : SysuiTestCase() {
@Before
fun setUp() {
- val sceneTestUtils = SceneTestUtils(this)
- sceneContainerRepository = sceneTestUtils.fakeSceneContainerRepository()
+ val kosmos = testKosmos()
+ sceneContainerRepository = kosmos.sceneContainerRepository
featureFlagsClassic = FakeFeatureFlagsClassic()
featureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
new file mode 100644
index 000000000000..c4a8582d51b5
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.communal.data.repository
+
+import android.content.pm.UserInfo
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryImpl.Companion.CURRENT_TUTORIAL_VERSION
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var secureSettings: FakeSettings
+ private lateinit var userRepository: FakeUserRepository
+
+ private lateinit var underTest: CommunalTutorialRepositoryImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ secureSettings = FakeSettings()
+ userRepository = FakeUserRepository()
+ val listOfUserInfo = listOf(MAIN_USER_INFO)
+ userRepository.setUserInfos(listOfUserInfo)
+
+ underTest =
+ CommunalTutorialRepositoryImpl(
+ kosmos.applicationCoroutineScope,
+ kosmos.testDispatcher,
+ userRepository,
+ secureSettings,
+ logcatLogBuffer("CommunalTutorialRepositoryImplTest"),
+ )
+ }
+
+ @Test
+ fun tutorialSettingState_defaultToNotStarted() =
+ testScope.runTest {
+ val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+ assertThat(tutorialSettingState)
+ .isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED)
+ }
+
+ @Test
+ fun tutorialSettingState_whenTutorialSettingsUpdatedToStarted() =
+ testScope.runTest {
+ underTest.setTutorialState(Settings.Secure.HUB_MODE_TUTORIAL_STARTED)
+ val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+ assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_STARTED)
+ }
+
+ @Test
+ fun tutorialSettingState_whenTutorialSettingsUpdatedToCompleted() =
+ testScope.runTest {
+ underTest.setTutorialState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+ val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+ assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+ }
+
+ @Test
+ fun tutorialVersion_userCompletedCurrentVersion_stateCompleted() =
+ testScope.runTest {
+ // User completed the current version.
+ setTutorialStateSetting(CURRENT_TUTORIAL_VERSION)
+
+ // Verify tutorial state is completed.
+ val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+ assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+ }
+
+ @Test
+ fun tutorialVersion_userCompletedPreviousVersion_stateNotStarted() =
+ testScope.runTest {
+ // User completed the previous version.
+ setTutorialStateSetting(CURRENT_TUTORIAL_VERSION - 1)
+
+ // Verify tutorial state is not started.
+ val tutorialSettingState by collectLastValue(underTest.tutorialSettingState)
+ assertThat(tutorialSettingState)
+ .isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED)
+ }
+
+ @Test
+ fun tutorialVersion_uponTutorialCompletion_writeCurrentVersion() =
+ testScope.runTest {
+ // Tutorial not started.
+ setTutorialStateSetting(Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED)
+
+ // Tutorial completed.
+ underTest.setTutorialState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+ // Verify tutorial setting state is updated to current version.
+ val settingState = getTutorialStateSetting()
+ assertThat(settingState).isEqualTo(CURRENT_TUTORIAL_VERSION)
+ }
+
+ private fun setTutorialStateSetting(
+ @Settings.Secure.HubModeTutorialState state: Int,
+ user: UserInfo = MAIN_USER_INFO
+ ) {
+ secureSettings.putIntForUser(Settings.Secure.HUB_MODE_TUTORIAL_STATE, state, user.id)
+ }
+
+ private fun getTutorialStateSetting(user: UserInfo = MAIN_USER_INFO): Int {
+ return secureSettings.getIntForUser(Settings.Secure.HUB_MODE_TUTORIAL_STATE, user.id)
+ }
+
+ companion object {
+ private val MAIN_USER_INFO =
+ UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
index 4079f1241f31..fdb17c298e17 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryImplTest.kt
@@ -16,14 +16,9 @@
package com.android.systemui.communal.data.repository
-import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
-import android.content.Intent
-import android.content.Intent.ACTION_USER_UNLOCKED
-import android.os.UserHandle
-import android.os.UserManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -32,11 +27,14 @@ import com.android.systemui.communal.data.db.CommunalWidgetDao
import com.android.systemui.communal.data.db.CommunalWidgetItem
import com.android.systemui.communal.shared.CommunalWidgetHost
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.res.R
-import com.android.systemui.settings.UserTracker
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -44,8 +42,6 @@ import java.util.Optional
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -65,13 +61,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var appWidgetManager: AppWidgetManager
- @Mock private lateinit var appWidgetHost: AppWidgetHost
-
- @Mock private lateinit var userManager: UserManager
-
- @Mock private lateinit var userHandle: UserHandle
-
- @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
@Mock private lateinit var stopwatchProviderInfo: AppWidgetProviderInfo
@@ -81,13 +71,11 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var communalWidgetDao: CommunalWidgetDao
- private lateinit var communalRepository: FakeCommunalRepository
+ private val kosmos = testKosmos()
- private lateinit var logBuffer: LogBuffer
+ private val testScope = kosmos.testScope
- private val testDispatcher = StandardTestDispatcher()
-
- private val testScope = TestScope(testDispatcher)
+ private lateinit var logBuffer: LogBuffer
private val fakeAllowlist =
listOf(
@@ -96,68 +84,60 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
"com.android.fake/WidgetProviderC",
)
+ private lateinit var underTest: CommunalWidgetRepositoryImpl
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- logBuffer = FakeLogBuffer.Factory.create()
- communalRepository = FakeCommunalRepository()
+ logBuffer = logcatLogBuffer(name = "CommunalWidgetRepoImplTest")
- communalEnabled(true)
setAppWidgetIds(emptyList())
overrideResource(R.array.config_communalWidgetAllowlist, fakeAllowlist.toTypedArray())
whenever(stopwatchProviderInfo.loadLabel(any())).thenReturn("Stopwatch")
- whenever(userTracker.userHandle).thenReturn(userHandle)
whenever(communalWidgetDao.getWidgets()).thenReturn(flowOf(emptyMap()))
whenever(appWidgetManagerOptional.isPresent).thenReturn(true)
whenever(appWidgetManagerOptional.get()).thenReturn(appWidgetManager)
+
+ underTest =
+ CommunalWidgetRepositoryImpl(
+ appWidgetManagerOptional,
+ appWidgetHost,
+ testScope.backgroundScope,
+ kosmos.testDispatcher,
+ communalWidgetHost,
+ communalWidgetDao,
+ logBuffer,
+ )
}
@Test
- fun neverQueryDbForWidgets_whenFeatureIsDisabled() =
+ fun neverQueryDbForWidgets_whenHostIsInactive() =
testScope.runTest {
- communalEnabled(false)
- val repository = initCommunalWidgetRepository()
- repository.communalWidgets.launchIn(backgroundScope)
+ underTest.updateAppWidgetHostActive(false)
+ underTest.communalWidgets.launchIn(testScope.backgroundScope)
runCurrent()
verify(communalWidgetDao, never()).getWidgets()
}
@Test
- fun neverQueryDbForWidgets_whenFeatureEnabled_andUserLocked() =
+ fun communalWidgets_whenHostIsActive_queryWidgetsFromDb() =
testScope.runTest {
- userUnlocked(false)
- val repository = initCommunalWidgetRepository()
- repository.communalWidgets.launchIn(backgroundScope)
- runCurrent()
-
- verify(communalWidgetDao, never()).getWidgets()
- }
+ underTest.updateAppWidgetHostActive(true)
- @Test
- fun communalWidgets_whenUserUnlocked_queryWidgetsFromDb() =
- testScope.runTest {
- userUnlocked(false)
- val repository = initCommunalWidgetRepository()
- val communalWidgets by collectLastValue(repository.communalWidgets)
- runCurrent()
val communalItemRankEntry = CommunalItemRank(uid = 1L, rank = 1)
val communalWidgetItemEntry = CommunalWidgetItem(uid = 1L, 1, "pk_name/cls_name", 1L)
whenever(communalWidgetDao.getWidgets())
.thenReturn(flowOf(mapOf(communalItemRankEntry to communalWidgetItemEntry)))
whenever(appWidgetManager.getAppWidgetInfo(anyInt())).thenReturn(providerInfoA)
- userUnlocked(true)
installedProviders(listOf(stopwatchProviderInfo))
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(ACTION_USER_UNLOCKED)
- )
- runCurrent()
+ val communalWidgets by collectLastValue(underTest.communalWidgets)
+ runCurrent()
verify(communalWidgetDao).getWidgets()
assertThat(communalWidgets)
.containsExactly(
@@ -172,9 +152,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun addWidget_allocateId_bindWidget_andAddToDb() =
testScope.runTest {
- userUnlocked(true)
- val repository = initCommunalWidgetRepository()
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
@@ -182,7 +160,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
.thenReturn(id)
- repository.addWidget(provider, priority) { true }
+ underTest.addWidget(provider, priority) { true }
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -192,16 +170,14 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun addWidget_configurationFails_doNotAddWidgetToDb() =
testScope.runTest {
- userUnlocked(true)
- val repository = initCommunalWidgetRepository()
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
- repository.addWidget(provider, priority) { false }
+ underTest.addWidget(provider, priority) { false }
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -212,16 +188,14 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun addWidget_configurationThrowsError_doNotAddWidgetToDb() =
testScope.runTest {
- userUnlocked(true)
- val repository = initCommunalWidgetRepository()
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
val priority = 1
whenever(communalWidgetHost.requiresConfiguration(id)).thenReturn(true)
whenever(communalWidgetHost.allocateIdAndBindWidget(provider)).thenReturn(id)
- repository.addWidget(provider, priority) { throw IllegalStateException("some error") }
+ underTest.addWidget(provider, priority) { throw IllegalStateException("some error") }
runCurrent()
verify(communalWidgetHost).allocateIdAndBindWidget(provider)
@@ -232,9 +206,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun addWidget_configurationNotRequired_doesNotConfigure_addWidgetToDb() =
testScope.runTest {
- userUnlocked(true)
- val repository = initCommunalWidgetRepository()
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
val provider = ComponentName("pkg_name", "cls_name")
val id = 1
@@ -243,7 +215,7 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
whenever(communalWidgetHost.allocateIdAndBindWidget(any<ComponentName>()))
.thenReturn(id)
var configured = false
- repository.addWidget(provider, priority) {
+ underTest.addWidget(provider, priority) {
configured = true
true
}
@@ -257,12 +229,10 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun deleteWidget_removeWidgetId_andDeleteFromDb() =
testScope.runTest {
- userUnlocked(true)
- val repository = initCommunalWidgetRepository()
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
val id = 1
- repository.deleteWidget(id)
+ underTest.deleteWidget(id)
runCurrent()
verify(communalWidgetDao).deleteWidgetById(id)
@@ -272,108 +242,37 @@ class CommunalWidgetRepositoryImplTest : SysuiTestCase() {
@Test
fun reorderWidgets_queryDb() =
testScope.runTest {
- userUnlocked(true)
- val repository = initCommunalWidgetRepository()
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
val widgetIdToPriorityMap = mapOf(104 to 1, 103 to 2, 101 to 3)
- repository.updateWidgetOrder(widgetIdToPriorityMap)
+ underTest.updateWidgetOrder(widgetIdToPriorityMap)
runCurrent()
verify(communalWidgetDao).updateWidgetOrder(widgetIdToPriorityMap)
}
@Test
- fun broadcastReceiver_communalDisabled_doNotRegisterUserUnlockedBroadcastReceiver() =
+ fun appWidgetHost_startListening() =
testScope.runTest {
- communalEnabled(false)
- val repository = initCommunalWidgetRepository()
- repository.communalWidgets.launchIn(backgroundScope)
- runCurrent()
- assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(0)
- }
-
- @Test
- fun broadcastReceiver_featureEnabledAndUserLocked_registerBroadcastReceiver() =
- testScope.runTest {
- userUnlocked(false)
- val repository = initCommunalWidgetRepository()
- repository.communalWidgets.launchIn(backgroundScope)
- runCurrent()
- assertThat(fakeBroadcastDispatcher.numReceiversRegistered).isEqualTo(1)
- }
-
- @Test
- fun appWidgetHost_userUnlocked_startListening() =
- testScope.runTest {
- userUnlocked(false)
- val repository = initCommunalWidgetRepository()
- repository.communalWidgets.launchIn(backgroundScope)
- runCurrent()
verify(appWidgetHost, never()).startListening()
- userUnlocked(true)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(ACTION_USER_UNLOCKED)
- )
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
verify(appWidgetHost).startListening()
}
@Test
- fun appWidgetHost_userLockedAgain_stopListening() =
+ fun appWidgetHost_stopListening() =
testScope.runTest {
- userUnlocked(false)
- val repository = initCommunalWidgetRepository()
- repository.communalWidgets.launchIn(backgroundScope)
- runCurrent()
-
- userUnlocked(true)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(ACTION_USER_UNLOCKED)
- )
- runCurrent()
+ underTest.updateAppWidgetHostActive(true)
verify(appWidgetHost).startListening()
- verify(appWidgetHost, never()).stopListening()
- userUnlocked(false)
- fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
- context,
- Intent(ACTION_USER_UNLOCKED)
- )
- runCurrent()
+ underTest.updateAppWidgetHostActive(false)
verify(appWidgetHost).stopListening()
}
- private fun initCommunalWidgetRepository(): CommunalWidgetRepositoryImpl {
- return CommunalWidgetRepositoryImpl(
- appWidgetManagerOptional,
- appWidgetHost,
- testScope.backgroundScope,
- testDispatcher,
- fakeBroadcastDispatcher,
- communalRepository,
- communalWidgetHost,
- communalWidgetDao,
- userManager,
- userTracker,
- logBuffer,
- )
- }
-
- private fun communalEnabled(enabled: Boolean) {
- communalRepository.setIsCommunalEnabled(enabled)
- }
-
- private fun userUnlocked(userUnlocked: Boolean) {
- whenever(userManager.isUserUnlockingOrUnlocked(userHandle)).thenReturn(userUnlocked)
- }
-
private fun installedProviders(providers: List<AppWidgetProviderInfo>) {
whenever(appWidgetManager.installedProviders).thenReturn(providers)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.kt
new file mode 100644
index 000000000000..ed29aa4ac202
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorCommunalDisabledTest.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.systemui.communal.domain.interactor
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.data.repository.FakeCommunalRepository
+import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.communalInteractor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * This class is a variation of the [CommunalInteractorTest] for cases where communal is disabled.
+ */
+@SmallTest
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class CommunalInteractorCommunalDisabledTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var communalRepository: FakeCommunalRepository
+ private lateinit var widgetRepository: FakeCommunalWidgetRepository
+ private lateinit var keyguardRepository: FakeKeyguardRepository
+
+ private lateinit var underTest: CommunalInteractor
+
+ @Before
+ fun setUp() {
+ communalRepository = kosmos.fakeCommunalRepository
+ widgetRepository = kosmos.fakeCommunalWidgetRepository
+ keyguardRepository = kosmos.fakeKeyguardRepository
+
+ communalRepository.setIsCommunalEnabled(false)
+
+ underTest = kosmos.communalInteractor
+ }
+
+ @Test
+ fun isCommunalEnabled_false() =
+ testScope.runTest { assertThat(underTest.isCommunalEnabled).isFalse() }
+
+ @Test
+ fun isCommunalAvailable_whenStorageUnlock_false() =
+ testScope.runTest {
+ val isCommunalAvailable by collectLastValue(underTest.isCommunalAvailable)
+
+ assertThat(isCommunalAvailable).isFalse()
+
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ runCurrent()
+
+ assertThat(isCommunalAvailable).isFalse()
+ }
+
+ @Test
+ fun updateAppWidgetHostActive_whenStorageUnlock_false() =
+ testScope.runTest {
+ assertThat(widgetRepository.isHostActive()).isFalse()
+
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ runCurrent()
+
+ assertThat(widgetRepository.isHostActive()).isFalse()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index cd83c07a3b38..7769223c8af2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -24,9 +24,15 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
+import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.fakeCommunalPrefsRepository
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.data.repository.fakeCommunalTutorialRepository
+import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.model.CommunalContentSize
import com.android.systemui.communal.shared.model.CommunalSceneKey
@@ -35,15 +41,19 @@ import com.android.systemui.communal.shared.model.ObservableCommunalTransitionSt
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.communalInteractor
+import com.android.systemui.keyguard.domain.interactor.editWidgetsActivityStarter
+import com.android.systemui.kosmos.testScope
import com.android.systemui.smartspace.data.repository.FakeSmartspaceRepository
+import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -51,13 +61,17 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
+/**
+ * This class of test cases assume that communal is enabled. For disabled cases, see
+ * [CommunalInteractorCommunalDisabledTest].
+ */
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
class CommunalInteractorTest : SysuiTestCase() {
- private lateinit var testScope: TestScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var tutorialRepository: FakeCommunalTutorialRepository
private lateinit var communalRepository: FakeCommunalRepository
@@ -65,41 +79,63 @@ class CommunalInteractorTest : SysuiTestCase() {
private lateinit var widgetRepository: FakeCommunalWidgetRepository
private lateinit var smartspaceRepository: FakeSmartspaceRepository
private lateinit var keyguardRepository: FakeKeyguardRepository
+ private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter
private lateinit var underTest: CommunalInteractor
@Before
fun setUp() {
- MockitoAnnotations.initMocks(this)
+ tutorialRepository = kosmos.fakeCommunalTutorialRepository
+ communalRepository = kosmos.fakeCommunalRepository
+ mediaRepository = kosmos.fakeCommunalMediaRepository
+ widgetRepository = kosmos.fakeCommunalWidgetRepository
+ smartspaceRepository = kosmos.fakeSmartspaceRepository
+ keyguardRepository = kosmos.fakeKeyguardRepository
+ editWidgetsActivityStarter = kosmos.editWidgetsActivityStarter
+ communalPrefsRepository = kosmos.fakeCommunalPrefsRepository
+
+ underTest = kosmos.communalInteractor
+ }
- testScope = TestScope(StandardTestDispatcher())
+ @Test
+ fun communalEnabled_true() =
+ testScope.runTest { assertThat(underTest.isCommunalEnabled).isTrue() }
- val withDeps = CommunalInteractorFactory.create(testScope)
+ @Test
+ fun isCommunalAvailable_trueWhenStorageUnlock() =
+ testScope.runTest {
+ val isAvailable by collectLastValue(underTest.isCommunalAvailable)
+ assertThat(isAvailable).isFalse()
- tutorialRepository = withDeps.tutorialRepository
- communalRepository = withDeps.communalRepository
- mediaRepository = withDeps.mediaRepository
- widgetRepository = withDeps.widgetRepository
- smartspaceRepository = withDeps.smartspaceRepository
- keyguardRepository = withDeps.keyguardRepository
- editWidgetsActivityStarter = withDeps.editWidgetsActivityStarter
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ runCurrent()
- underTest = withDeps.communalInteractor
- }
+ assertThat(isAvailable).isTrue()
+ }
@Test
- fun communalEnabled() =
+ fun isCommunalAvailable_whenStorageUnlock_true() =
testScope.runTest {
- communalRepository.setIsCommunalEnabled(true)
- assertThat(underTest.isCommunalEnabled).isTrue()
+ val isAvailable by collectLastValue(underTest.isCommunalAvailable)
+ assertThat(isAvailable).isFalse()
+
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ runCurrent()
+
+ assertThat(isAvailable).isTrue()
}
@Test
- fun communalDisabled() =
+ fun updateAppWidgetHostActive_uponStorageUnlock_true() =
testScope.runTest {
- communalRepository.setIsCommunalEnabled(false)
- assertThat(underTest.isCommunalEnabled).isFalse()
+ collectLastValue(underTest.isCommunalAvailable)
+ assertThat(widgetRepository.isHostActive()).isFalse()
+
+ keyguardRepository.setIsEncryptedOrLockdown(false)
+ runCurrent()
+
+ assertThat(widgetRepository.isHostActive()).isTrue()
}
@Test
@@ -331,10 +367,9 @@ class CommunalInteractorTest : SysuiTestCase() {
}
@Test
- fun cta_visibilityTrue_shows() =
+ fun ctaTile_showsByDefault() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- communalRepository.setCtaTileInViewModeVisibility(true)
val ctaTileContent by collectLastValue(underTest.ctaTileContent)
@@ -346,10 +381,10 @@ class CommunalInteractorTest : SysuiTestCase() {
}
@Test
- fun ctaTile_visibilityFalse_doesNotShow() =
+ fun ctaTile_afterDismiss_doesNotShow() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
- communalRepository.setCtaTileInViewModeVisibility(false)
+ communalPrefsRepository.setCtaDismissedForCurrentUser()
val ctaTileContent by collectLastValue(underTest.ctaTileContent)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
new file mode 100644
index 000000000000..e904236f8c3e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/ui/widgets/CommunalAppWidgetHostTest.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.ui.widgets
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
+import com.android.systemui.communal.widgets.CommunalAppWidgetHostView
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class CommunalAppWidgetHostTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var underTest: CommunalAppWidgetHost
+
+ @Before
+ fun setUp() {
+ underTest = CommunalAppWidgetHost(context = context, hostId = 116)
+ }
+
+ @Test
+ fun createViewForCommunal_returnCommunalAppWidgetView() =
+ testScope.runTest {
+ val appWidgetId = 789
+ val view =
+ underTest.createViewForCommunal(
+ context = context,
+ appWidgetId = appWidgetId,
+ appWidget = null
+ )
+ assertThat(view).isInstanceOf(CommunalAppWidgetHostView::class.java)
+ assertThat(view).isNotNull()
+ assertThat(view.appWidgetId).isEqualTo(appWidgetId)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 54510a82201a..09243e5282da 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -19,7 +19,6 @@ package com.android.systemui.communal.view.viewmodel
import android.app.Activity.RESULT_CANCELED
import android.app.Activity.RESULT_OK
import android.app.smartspace.SmartspaceTarget
-import android.appwidget.AppWidgetHost
import android.content.ComponentName
import android.provider.Settings
import android.widget.RemoteViews
@@ -36,6 +35,7 @@ import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.log.CommunalUiEvent
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.kosmos.testScope
@@ -61,7 +61,7 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
class CommunalEditModeViewModelTest : SysuiTestCase() {
@Mock private lateinit var mediaHost: MediaHost
- @Mock private lateinit var appWidgetHost: AppWidgetHost
+ @Mock private lateinit var appWidgetHost: CommunalAppWidgetHost
@Mock private lateinit var uiEventLogger: UiEventLogger
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 804c0528c46c..f9cfc3732a01 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -23,6 +23,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
@@ -66,6 +67,7 @@ class CommunalViewModelTest : SysuiTestCase() {
private lateinit var widgetRepository: FakeCommunalWidgetRepository
private lateinit var smartspaceRepository: FakeSmartspaceRepository
private lateinit var mediaRepository: FakeCommunalMediaRepository
+ private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository
private lateinit var underTest: CommunalViewModel
@@ -82,6 +84,7 @@ class CommunalViewModelTest : SysuiTestCase() {
widgetRepository = withDeps.widgetRepository
smartspaceRepository = withDeps.smartspaceRepository
mediaRepository = withDeps.mediaRepository
+ communalPrefsRepository = withDeps.communalPrefsRepository
underTest =
CommunalViewModel(
@@ -149,9 +152,6 @@ class CommunalViewModelTest : SysuiTestCase() {
// Media playing.
mediaRepository.mediaActive()
- // CTA Tile not dismissed.
- communalRepository.setCtaTileInViewModeVisibility(true)
-
val communalContent by collectLastValue(underTest.communalContent)
// Order is smart space, then UMO, widget content and cta tile.
@@ -171,7 +171,6 @@ class CommunalViewModelTest : SysuiTestCase() {
fun dismissCta_hidesCtaTileAndShowsPopup_thenHidesPopupAfterTimeout() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
- communalRepository.setCtaTileInViewModeVisibility(true)
val communalContent by collectLastValue(underTest.communalContent)
val isPopupOnDismissCtaShowing by collectLastValue(underTest.isPopupOnDismissCtaShowing)
@@ -195,7 +194,6 @@ class CommunalViewModelTest : SysuiTestCase() {
fun popup_onDismiss_hidesImmediately() =
testScope.runTest {
tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
- communalRepository.setCtaTileInViewModeVisibility(true)
val isPopupOnDismissCtaShowing by collectLastValue(underTest.isPopupOnDismissCtaShowing)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
index 565049baf6f4..b54c5bdae004 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -7,9 +7,11 @@ import com.android.internal.widget.LockPatternUtils
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.whenever
@@ -36,8 +38,8 @@ class DeviceEntryRepositoryTest : SysuiTestCase() {
@Mock private lateinit var keyguardBypassController: KeyguardBypassController
@Mock private lateinit var keyguardStateController: KeyguardStateController
- private val testUtils = SceneTestUtils(this)
- private val testScope = testUtils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private val userRepository = FakeUserRepository()
private val keyguardRepository = FakeKeyguardRepository()
@@ -52,7 +54,7 @@ class DeviceEntryRepositoryTest : SysuiTestCase() {
underTest =
DeviceEntryRepositoryImpl(
applicationScope = testScope.backgroundScope,
- backgroundDispatcher = testUtils.testDispatcher,
+ backgroundDispatcher = kosmos.testDispatcher,
userRepository = userRepository,
lockPatternUtils = lockPatternUtils,
keyguardBypassController = keyguardBypassController,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 929e879a5406..62d23152b77a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -20,14 +20,20 @@ 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.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
@@ -41,18 +47,18 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class DeviceEntryInteractorTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val faceAuthRepository = utils.kosmos.fakeDeviceEntryFaceAuthRepository
- private val trustRepository = utils.kosmos.fakeTrustRepository
- private val sceneInteractor = utils.sceneInteractor()
- private val authenticationInteractor = utils.authenticationInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
+ private val trustRepository = kosmos.fakeTrustRepository
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val authenticationInteractor = kosmos.authenticationInteractor
private lateinit var underTest: DeviceEntryInteractor
@Before
fun setUp() {
- utils.fakeSceneContainerFlags.enabled = true
- underTest = utils.deviceEntryInteractor()
+ kosmos.fakeSceneContainerFlags.enabled = true
+ underTest = kosmos.deviceEntryInteractor
}
@Test
@@ -65,8 +71,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
@Test
fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.deviceEntryRepository.apply {
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ kosmos.fakeDeviceEntryRepository.apply {
setLockscreenEnabled(false)
// Toggle isUnlocked, twice.
@@ -99,8 +107,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
@Test
fun isUnlocked_whenAuthMethodIsSimAndUnlocked_isFalse() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim)
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Sim
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
val isUnlocked by collectLastValue(underTest.isUnlocked)
assertThat(isUnlocked).isFalse()
@@ -157,10 +167,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
@Test
fun isDeviceEntered_onBouncer_isFalse() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Pattern
)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
switchToScene(SceneKey.Lockscreen)
runCurrent()
switchToScene(SceneKey.Bouncer)
@@ -182,8 +192,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
@Test
fun canSwipeToEnter_onLockscreenWithPin_isFalse() =
testScope.runTest {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
switchToScene(SceneKey.Lockscreen)
val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
@@ -203,15 +215,15 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
}
private fun setupSwipeDeviceEntryMethod() {
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
}
@Test
fun canSwipeToEnter_whenTrustedByTrustManager_isTrue() =
testScope.runTest {
val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
switchToScene(SceneKey.Lockscreen)
@@ -228,7 +240,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
fun canSwipeToEnter_whenAuthenticatedByFace_isTrue() =
testScope.runTest {
val canSwipeToEnter by collectLastValue(underTest.canSwipeToEnter)
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
switchToScene(SceneKey.Lockscreen)
@@ -244,9 +256,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
@Test
fun isAuthenticationRequired_lockedAndSecured_true() =
testScope.runTest {
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
@@ -256,9 +268,11 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
@Test
fun isAuthenticationRequired_lockedAndNotSecured_false() =
testScope.runTest {
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
assertThat(underTest.isAuthenticationRequired()).isFalse()
}
@@ -266,9 +280,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
@Test
fun isAuthenticationRequired_unlockedAndSecured_false() =
testScope.runTest {
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
AuthenticationMethodModel.Password
)
@@ -278,9 +292,11 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
@Test
fun isAuthenticationRequired_unlockedAndNotSecured_false() =
testScope.runTest {
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
runCurrent()
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
assertThat(underTest.isAuthenticationRequired()).isFalse()
}
@@ -288,7 +304,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
@Test
fun isBypassEnabled_enabledInRepository_true() =
testScope.runTest {
- utils.deviceEntryRepository.setBypassEnabled(true)
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(true)
assertThat(underTest.isBypassEnabled.value).isTrue()
}
@@ -299,8 +315,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
switchToScene(SceneKey.Lockscreen)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
runCurrent()
underTest.attemptDeviceEntry()
@@ -315,7 +333,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
switchToScene(SceneKey.Lockscreen)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
underTest.attemptDeviceEntry()
@@ -329,8 +349,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
switchToScene(SceneKey.Lockscreen)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
- utils.deviceEntryRepository.setLockscreenEnabled(true)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
underTest.attemptDeviceEntry()
@@ -340,7 +362,7 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
@Test
fun isBypassEnabled_disabledInRepository_false() =
testScope.runTest {
- utils.deviceEntryRepository.setBypassEnabled(false)
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(false)
assertThat(underTest.isBypassEnabled.value).isFalse()
}
@@ -348,8 +370,10 @@ class DeviceEntryInteractorTest : SysuiTestCase() {
fun successfulAuthenticationChallengeAttempt_updatesIsUnlockedState() =
testScope.runTest {
val isUnlocked by collectLastValue(underTest.isUnlocked)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
assertThat(isUnlocked).isFalse()
authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index c4ebbdcc2f58..6f62afc560fe 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -35,6 +35,7 @@ import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
@@ -52,6 +53,8 @@ import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.verify
@@ -68,6 +71,8 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var authController: AuthController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
+ @Mock private lateinit var userTracker: UserTracker
+ @Captor private lateinit var updateCallbackCaptor: ArgumentCaptor<KeyguardUpdateMonitorCallback>
private val mainDispatcher = StandardTestDispatcher()
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -93,6 +98,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
testScope.backgroundScope,
systemClock,
facePropertyRepository,
+ userTracker,
)
}
@@ -553,4 +559,25 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
job.cancel()
}
+
+ @Test
+ fun isEncryptedOrLockdown() =
+ testScope.runTest {
+ whenever(userTracker.userId).thenReturn(0)
+ whenever(keyguardUpdateMonitor.isEncryptedOrLockdown(0)).thenReturn(true)
+
+ // Default value for isEncryptedOrLockdown is true
+ val isEncryptedOrLockdown by collectLastValue(underTest.isEncryptedOrLockdown)
+ assertThat(isEncryptedOrLockdown).isTrue()
+
+ verify(keyguardUpdateMonitor).registerCallback(updateCallbackCaptor.capture())
+ val updateCallback = updateCallbackCaptor.value
+
+ // Strong auth state updated
+ whenever(keyguardUpdateMonitor.isEncryptedOrLockdown(0)).thenReturn(false)
+ updateCallback.onStrongAuthStateChanged(0)
+
+ // Verify no longer encrypted or lockdown
+ assertThat(isEncryptedOrLockdown).isFalse()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
index 8933d2c93f5a..2c3afb1b40a9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt
@@ -26,12 +26,16 @@ import com.android.systemui.common.ui.data.repository.FakeConfigurationRepositor
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.shared.model.CameraLaunchSourceModel
+import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.shade.data.repository.FakeShadeRepository
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -48,10 +52,10 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class KeyguardInteractorTest : SysuiTestCase() {
- private val testUtils = SceneTestUtils(this)
- private val testScope = testUtils.testScope
- private val repository = testUtils.keyguardRepository
- private val sceneInteractor = testUtils.sceneInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val repository = kosmos.fakeKeyguardRepository
+ private val sceneInteractor = kosmos.sceneInteractor
private val commandQueue = FakeCommandQueue()
private val bouncerRepository = FakeKeyguardBouncerRepository()
private val shadeRepository = FakeShadeRepository()
@@ -63,7 +67,7 @@ class KeyguardInteractorTest : SysuiTestCase() {
repository = repository,
commandQueue = commandQueue,
powerInteractor = PowerInteractorFactory.create().powerInteractor,
- sceneContainerFlags = testUtils.fakeSceneContainerFlags,
+ sceneContainerFlags = kosmos.fakeSceneContainerFlags,
bouncerRepository = bouncerRepository,
configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
shadeRepository = shadeRepository,
@@ -183,7 +187,7 @@ class KeyguardInteractorTest : SysuiTestCase() {
@Test
fun animationDozingTransitions() =
testScope.runTest {
- testUtils.fakeSceneContainerFlags.enabled = true
+ kosmos.fakeSceneContainerFlags.enabled = true
val isAnimate by collectLastValue(underTest.animateDozingTransitions)
underTest.setAnimateDozingTransitions(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
index 4f7d9444020c..6828041eff5a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -21,23 +21,24 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectValues
-import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
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.OFF
import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
import com.android.systemui.keyguard.shared.model.TransitionState.CANCELED
import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertEquals
-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
@@ -46,18 +47,11 @@ import org.junit.runner.RunWith
@kotlinx.coroutines.ExperimentalCoroutinesApi
class KeyguardTransitionInteractorTest : SysuiTestCase() {
- private lateinit var underTest: KeyguardTransitionInteractor
- private lateinit var repository: FakeKeyguardTransitionRepository
- private val testScope = TestScope()
-
- @Before
- fun setUp() {
- repository = FakeKeyguardTransitionRepository()
- underTest = KeyguardTransitionInteractorFactory.create(
- scope = testScope.backgroundScope,
- repository = repository,
- ).keyguardTransitionInteractor
- }
+ val kosmos = testKosmos()
+
+ val underTest = kosmos.keyguardTransitionInteractor
+ val repository = kosmos.fakeKeyguardTransitionRepository
+ val testScope = kosmos.testScope
@Test
fun transitionCollectorsReceivesOnlyAppropriateEvents() = runTest {
@@ -114,49 +108,51 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() {
}
@Test
- fun finishedKeyguardStateTests() = testScope.runTest {
- val finishedSteps by collectValues(underTest.finishedKeyguardState)
- runCurrent()
- val steps = mutableListOf<TransitionStep>()
-
- steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
- steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
- steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
- steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
- steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
- steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
- steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
-
- steps.forEach {
- repository.sendTransitionStep(it)
+ fun finishedKeyguardStateTests() =
+ testScope.runTest {
+ val finishedSteps by collectValues(underTest.finishedKeyguardState)
runCurrent()
+ val steps = mutableListOf<TransitionStep>()
+
+ steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
+ steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
+ steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
+ steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
+ steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
+ steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+ steps.forEach {
+ repository.sendTransitionStep(it)
+ runCurrent()
+ }
+
+ assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD))
}
- assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD))
- }
-
@Test
- fun startedKeyguardStateTests() = testScope.runTest {
- val startedStates by collectValues(underTest.startedKeyguardState)
- runCurrent()
- val steps = mutableListOf<TransitionStep>()
-
- steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
- steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
- steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
- steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
- steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
- steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
- steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
-
- steps.forEach {
- repository.sendTransitionStep(it)
+ fun startedKeyguardStateTests() =
+ testScope.runTest {
+ val startedStates by collectValues(underTest.startedKeyguardState)
runCurrent()
+ val steps = mutableListOf<TransitionStep>()
+
+ steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0f, STARTED))
+ steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 0.5f, RUNNING))
+ steps.add(TransitionStep(AOD, PRIMARY_BOUNCER, 1f, FINISHED))
+ steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0f, STARTED))
+ steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 0.9f, RUNNING))
+ steps.add(TransitionStep(PRIMARY_BOUNCER, AOD, 1f, FINISHED))
+ steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+ steps.forEach {
+ repository.sendTransitionStep(it)
+ runCurrent()
+ }
+
+ assertThat(startedStates).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD, GONE))
}
- assertThat(startedStates).isEqualTo(listOf(LOCKSCREEN, PRIMARY_BOUNCER, AOD, GONE))
- }
-
@Test
fun finishedKeyguardTransitionStepTests() = runTest {
val finishedSteps by collectValues(underTest.finishedKeyguardTransitionStep)
@@ -178,7 +174,7 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() {
// Ignore the default state.
assertThat(finishedSteps.subList(1, finishedSteps.size))
- .isEqualTo(listOf(steps[2], steps[5]))
+ .isEqualTo(listOf(steps[2], steps[5]))
}
@Test
@@ -233,500 +229,1067 @@ class KeyguardTransitionInteractorTest : SysuiTestCase() {
}
@Test
- fun isInTransitionToState() = testScope.runTest {
- val results by collectValues(underTest.isInTransitionToState(GONE))
+ fun isInTransitionToAnyState() =
+ testScope.runTest {
+ val inTransition by collectValues(underTest.isInTransitionToAnyState)
+
+ assertEquals(
+ listOf(
+ true, // The repo is seeded with a transition from OFF to LOCKSCREEN.
+ false,
+ ),
+ inTransition
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+ )
+
+ assertEquals(
+ listOf(
+ true,
+ false,
+ true,
+ ),
+ inTransition
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+ )
+
+ assertEquals(
+ listOf(
+ true,
+ false,
+ true,
+ ),
+ inTransition
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+ )
+
+ assertEquals(
+ listOf(
+ true,
+ false,
+ true,
+ false,
+ ),
+ inTransition
+ )
+ }
+
+ @Test
+ fun isInTransitionToAnyState_finishedStateIsStartedStateAfterCancels() =
+ testScope.runTest {
+ val inTransition by collectValues(underTest.isInTransitionToAnyState)
+
+ assertEquals(
+ listOf(
+ true,
+ false,
+ ),
+ inTransition
+ )
+
+ // Start FINISHED in GONE.
+ sendSteps(
+ TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+ TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+ TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+ )
+
+ assertEquals(
+ listOf(
+ true,
+ false,
+ true,
+ false,
+ ),
+ inTransition
+ )
+
+ sendSteps(
+ TransitionStep(GONE, DOZING, 0f, STARTED),
+ )
+
+ assertEquals(
+ listOf(
+ true,
+ false,
+ true,
+ false,
+ true,
+ ),
+ inTransition
+ )
+
+ sendSteps(
+ TransitionStep(GONE, DOZING, 0.5f, RUNNING),
+ TransitionStep(GONE, DOZING, 0.6f, CANCELED),
+ TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
+ TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING),
+ TransitionStep(DOZING, LOCKSCREEN, 0.6f, CANCELED),
+ TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+ )
+
+ assertEquals(
+ listOf(
+ true,
+ false,
+ true,
+ false,
+ // We should have been in transition throughout the entire transition, including
+ // both cancellations, and we should still be in transition despite now
+ // transitioning to GONE, the state we're also FINISHED in.
+ true,
+ ),
+ inTransition
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+ TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+ )
+
+ assertEquals(
+ listOf(
+ true,
+ false,
+ true,
+ false,
+ true,
+ false,
+ ),
+ inTransition
+ )
+ }
+
+ @Test
+ fun isInTransitionToState() =
+ testScope.runTest {
+ val results by collectValues(underTest.isInTransitionToState(GONE))
- sendSteps(
+ sendSteps(
TransitionStep(AOD, DOZING, 0f, STARTED),
TransitionStep(AOD, DOZING, 0.5f, RUNNING),
TransitionStep(AOD, DOZING, 1f, FINISHED),
- )
-
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(GONE, DOZING, 0f, STARTED),
TransitionStep(GONE, DOZING, 0f, RUNNING),
TransitionStep(GONE, DOZING, 1f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
-
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- true,
- ))
- }
+ )
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ true,
+ )
+ )
+ }
@Test
- fun isInTransitionFromState() = testScope.runTest {
- val results by collectValues(underTest.isInTransitionFromState(DOZING))
+ fun isInTransitionFromState() =
+ testScope.runTest {
+ val results by collectValues(underTest.isInTransitionFromState(DOZING))
- sendSteps(
+ sendSteps(
TransitionStep(AOD, DOZING, 0f, STARTED),
TransitionStep(AOD, DOZING, 0.5f, RUNNING),
TransitionStep(AOD, DOZING, 1f, FINISHED),
- )
-
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(GONE, DOZING, 0f, STARTED),
TransitionStep(GONE, DOZING, 0f, RUNNING),
TransitionStep(GONE, DOZING, 1f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
-
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- true,
- ))
- }
+ )
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ true,
+ )
+ )
+ }
@Test
- fun isInTransitionFromStateWhere() = testScope.runTest {
- val results by collectValues(underTest.isInTransitionFromStateWhere {
- it == DOZING
- })
+ fun isInTransitionFromStateWhere() =
+ testScope.runTest {
+ val results by collectValues(underTest.isInTransitionFromStateWhere { it == DOZING })
- sendSteps(
+ sendSteps(
TransitionStep(AOD, DOZING, 0f, STARTED),
TransitionStep(AOD, DOZING, 0.5f, RUNNING),
TransitionStep(AOD, DOZING, 1f, FINISHED),
- )
-
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(GONE, DOZING, 0f, STARTED),
TransitionStep(GONE, DOZING, 0f, RUNNING),
TransitionStep(GONE, DOZING, 1f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
-
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- true,
- ))
- }
+ )
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ true,
+ )
+ )
+ }
@Test
- fun isInTransitionWhere() = testScope.runTest {
- val results by collectValues(underTest.isInTransitionWhere(
- fromStatePredicate = { it == DOZING },
- toStatePredicate = { it == GONE },
- ))
+ fun isInTransitionWhere() =
+ testScope.runTest {
+ val results by
+ collectValues(
+ underTest.isInTransitionWhere(
+ fromStatePredicate = { it == DOZING },
+ toStatePredicate = { it == GONE },
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(AOD, DOZING, 0f, STARTED),
TransitionStep(AOD, DOZING, 0.5f, RUNNING),
TransitionStep(AOD, DOZING, 1f, FINISHED),
- )
-
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(GONE, DOZING, 0f, STARTED),
TransitionStep(GONE, DOZING, 0f, RUNNING),
TransitionStep(GONE, DOZING, 1f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
-
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- true,
- ))
- }
+ )
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ true,
+ )
+ )
+ }
@Test
- fun isFinishedInStateWhere() = testScope.runTest {
- val results by collectValues(underTest.isFinishedInStateWhere { it == GONE } )
+ fun isInTransitionWhere_withCanceledStep() =
+ testScope.runTest {
+ val results by
+ collectValues(
+ underTest.isInTransitionWhere(
+ fromStatePredicate = { it == DOZING },
+ toStatePredicate = { it == GONE },
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(AOD, DOZING, 0f, STARTED),
TransitionStep(AOD, DOZING, 0.5f, RUNNING),
TransitionStep(AOD, DOZING, 1f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false, // Finished in DOZING, not GONE.
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ )
+ )
- sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
+ sendSteps(
+ TransitionStep(DOZING, GONE, 0f, STARTED),
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
+ sendSteps(
+ TransitionStep(DOZING, GONE, 0f, RUNNING),
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
+ sendSteps(
+ TransitionStep(DOZING, GONE, 0f, CANCELED),
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(GONE, DOZING, 0f, STARTED),
TransitionStep(GONE, DOZING, 0f, RUNNING),
- )
+ TransitionStep(GONE, DOZING, 1f, FINISHED),
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
+
+ sendSteps(
+ TransitionStep(DOZING, GONE, 0f, STARTED),
+ TransitionStep(DOZING, GONE, 0f, RUNNING),
+ )
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ true,
+ )
+ )
+ }
+
+ @Test
+ fun isFinishedInStateWhere() =
+ testScope.runTest {
+ val results by collectValues(underTest.isFinishedInStateWhere { it == GONE })
+
+ sendSteps(
+ TransitionStep(AOD, DOZING, 0f, STARTED),
+ TransitionStep(AOD, DOZING, 0.5f, RUNNING),
+ TransitionStep(AOD, DOZING, 1f, FINISHED),
+ )
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false, // Finished in DOZING, not GONE.
+ )
+ )
+
+ sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ )
+ )
+
+ sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ )
+ )
+
+ sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
- sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ sendSteps(
+ TransitionStep(GONE, DOZING, 0f, STARTED),
+ TransitionStep(GONE, DOZING, 0f, RUNNING),
+ )
- sendSteps(
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
+
+ sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED))
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
+
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
-
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
-
- sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
-
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- true,
- ))
- }
+ )
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
+
+ sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ true,
+ )
+ )
+ }
@Test
- fun isFinishedInState() = testScope.runTest {
- val results by collectValues(underTest.isFinishedInState(GONE))
+ fun isFinishedInState() =
+ testScope.runTest {
+ val results by collectValues(underTest.isFinishedInState(GONE))
- sendSteps(
+ sendSteps(
TransitionStep(AOD, DOZING, 0f, STARTED),
TransitionStep(AOD, DOZING, 0.5f, RUNNING),
TransitionStep(AOD, DOZING, 1f, FINISHED),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false, // Finished in DOZING, not GONE.
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false, // Finished in DOZING, not GONE.
+ )
+ )
- sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
+ sendSteps(TransitionStep(DOZING, GONE, 0f, STARTED))
- assertThat(results).isEqualTo(listOf(
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ )
+ )
- sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
+ sendSteps(TransitionStep(DOZING, GONE, 0f, RUNNING))
- assertThat(results).isEqualTo(listOf(
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ )
+ )
- sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
+ sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(GONE, DOZING, 0f, STARTED),
TransitionStep(GONE, DOZING, 0f, RUNNING),
- )
+ )
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ )
+ )
- sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED))
+ sendSteps(TransitionStep(GONE, DOZING, 1f, FINISHED))
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
- sendSteps(
+ sendSteps(
TransitionStep(DOZING, GONE, 0f, STARTED),
TransitionStep(DOZING, GONE, 0f, RUNNING),
- )
-
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- ))
-
- sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
-
- assertThat(results).isEqualTo(listOf(
- false,
- true,
- false,
- true,
- ))
- }
+ )
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ )
+ )
+
+ sendSteps(TransitionStep(DOZING, GONE, 1f, FINISHED))
+
+ assertThat(results)
+ .isEqualTo(
+ listOf(
+ false,
+ true,
+ false,
+ true,
+ )
+ )
+ }
@Test
- fun finishedKeyguardState_emitsAgainIfCancelledAndReversed() = testScope.runTest {
- val finishedStates by collectValues(underTest.finishedKeyguardState)
+ fun finishedKeyguardState_emitsAgainIfCancelledAndReversed() =
+ testScope.runTest {
+ val finishedStates by collectValues(underTest.finishedKeyguardState)
- // We default FINISHED in LOCKSCREEN.
- assertEquals(listOf(
- LOCKSCREEN
- ), finishedStates)
+ // We default FINISHED in LOCKSCREEN.
+ assertEquals(listOf(LOCKSCREEN), finishedStates)
- sendSteps(
+ sendSteps(
TransitionStep(LOCKSCREEN, AOD, 0f, STARTED),
TransitionStep(LOCKSCREEN, AOD, 0.5f, RUNNING),
TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED),
- )
+ )
- // We're FINISHED in AOD.
- assertEquals(listOf(
- LOCKSCREEN,
- AOD,
- ), finishedStates)
+ // We're FINISHED in AOD.
+ assertEquals(
+ listOf(
+ LOCKSCREEN,
+ AOD,
+ ),
+ finishedStates
+ )
- // Transition back to LOCKSCREEN.
- sendSteps(
+ // Transition back to LOCKSCREEN.
+ sendSteps(
TransitionStep(AOD, LOCKSCREEN, 0f, STARTED),
TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING),
TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED),
- )
+ )
- // We're FINISHED in LOCKSCREEN.
- assertEquals(listOf(
- LOCKSCREEN,
- AOD,
- LOCKSCREEN,
- ), finishedStates)
+ // We're FINISHED in LOCKSCREEN.
+ assertEquals(
+ listOf(
+ LOCKSCREEN,
+ AOD,
+ LOCKSCREEN,
+ ),
+ finishedStates
+ )
- sendSteps(
+ sendSteps(
TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
- )
+ )
- // We've STARTED a transition to GONE but not yet finished it so we're still FINISHED in
- // LOCKSCREEN.
- assertEquals(listOf(
- LOCKSCREEN,
- AOD,
- LOCKSCREEN,
- ), finishedStates)
+ // We've STARTED a transition to GONE but not yet finished it so we're still FINISHED in
+ // LOCKSCREEN.
+ assertEquals(
+ listOf(
+ LOCKSCREEN,
+ AOD,
+ LOCKSCREEN,
+ ),
+ finishedStates
+ )
- sendSteps(
+ sendSteps(
TransitionStep(LOCKSCREEN, GONE, 0.6f, CANCELED),
- )
+ )
- // We've CANCELED a transition to GONE, we're still FINISHED in LOCKSCREEN.
- assertEquals(listOf(
- LOCKSCREEN,
- AOD,
- LOCKSCREEN,
- ), finishedStates)
+ // We've CANCELED a transition to GONE, we're still FINISHED in LOCKSCREEN.
+ assertEquals(
+ listOf(
+ LOCKSCREEN,
+ AOD,
+ LOCKSCREEN,
+ ),
+ finishedStates
+ )
- sendSteps(
+ sendSteps(
TransitionStep(GONE, LOCKSCREEN, 0.6f, STARTED),
TransitionStep(GONE, LOCKSCREEN, 0.9f, RUNNING),
TransitionStep(GONE, LOCKSCREEN, 1f, FINISHED),
- )
-
- // Expect another emission of LOCKSCREEN, as we have FINISHED a second transition to
- // LOCKSCREEN after the cancellation.
- assertEquals(listOf(
- LOCKSCREEN,
- AOD,
- LOCKSCREEN,
- LOCKSCREEN,
- ), finishedStates)
- }
+ )
+
+ // Expect another emission of LOCKSCREEN, as we have FINISHED a second transition to
+ // LOCKSCREEN after the cancellation.
+ assertEquals(
+ listOf(
+ LOCKSCREEN,
+ AOD,
+ LOCKSCREEN,
+ LOCKSCREEN,
+ ),
+ finishedStates
+ )
+ }
+
+ @Test
+ fun testCurrentState() =
+ testScope.runTest {
+ val currentStates by collectValues(underTest.currentKeyguardState)
+
+ // We init the repo with a transition from OFF -> LOCKSCREEN.
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, AOD, 0f, STARTED),
+ )
+
+ // The current state should continue to be LOCKSCREEN as we transition to AOD.
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, AOD, 0.5f, RUNNING),
+ )
+
+ // The current state should continue to be LOCKSCREEN as we transition to AOD.
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, AOD, 0.6f, CANCELED),
+ )
+
+ // Once CANCELED, we're still currently in LOCKSCREEN...
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(AOD, LOCKSCREEN, 0.6f, STARTED),
+ )
+
+ // ...until STARTING back to LOCKSCREEN, at which point the "current" state should be
+ // the
+ // one we're transitioning from, despite never FINISHING in that state.
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ AOD,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(AOD, LOCKSCREEN, 0.8f, RUNNING),
+ TransitionStep(AOD, LOCKSCREEN, 0.8f, FINISHED),
+ )
+
+ // FINSHING in LOCKSCREEN should update the current state to LOCKSCREEN.
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ AOD,
+ LOCKSCREEN,
+ ),
+ currentStates
+ )
+ }
+
+ @Test
+ fun testCurrentState_multipleCancellations_backToLastFinishedState() =
+ testScope.runTest {
+ val currentStates by collectValues(underTest.currentKeyguardState)
+
+ // We init the repo with a transition from OFF -> LOCKSCREEN.
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+ TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+ TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+ )
+
+ assertEquals(
+ listOf(
+ // Default transition from OFF -> LOCKSCREEN
+ OFF,
+ LOCKSCREEN,
+ // Transitioned to GONE
+ GONE,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(GONE, DOZING, 0f, STARTED),
+ TransitionStep(GONE, DOZING, 0.5f, RUNNING),
+ TransitionStep(GONE, DOZING, 0.6f, CANCELED),
+ )
+
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ GONE,
+ // Current state should not be DOZING until the post-cancelation transition is
+ // STARTED
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED),
+ )
+
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ GONE,
+ // DOZING -> LS STARTED, DOZING is now the current state.
+ DOZING,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING),
+ TransitionStep(DOZING, LOCKSCREEN, 0.6f, CANCELED),
+ )
+
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ GONE,
+ DOZING,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, GONE, 0f, STARTED),
+ )
+
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ GONE,
+ DOZING,
+ // LS -> GONE STARTED, LS is now the current state.
+ LOCKSCREEN,
+ ),
+ currentStates
+ )
+
+ sendSteps(
+ TransitionStep(LOCKSCREEN, GONE, 0.5f, RUNNING),
+ TransitionStep(LOCKSCREEN, GONE, 1f, FINISHED),
+ )
+
+ assertEquals(
+ listOf(
+ OFF,
+ LOCKSCREEN,
+ GONE,
+ DOZING,
+ LOCKSCREEN,
+ // FINISHED in GONE, GONE is now the current state.
+ GONE,
+ ),
+ currentStates
+ )
+ }
private suspend fun sendSteps(vararg steps: TransitionStep) {
steps.forEach {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 04e90c8deebf..aa15d0befa80 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -21,11 +21,19 @@ package com.android.systemui.keyguard.ui.viewmodel
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -37,9 +45,9 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class LockscreenSceneViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val sceneInteractor = utils.sceneInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
private val underTest = createLockscreenSceneViewModel()
@@ -47,9 +55,11 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
fun upTransitionSceneKey_canSwipeToUnlock_gone() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
@@ -59,8 +69,10 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
@@ -69,7 +81,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
@Test
fun leftTransitionSceneKey_communalIsEnabled_communal() =
testScope.runTest {
- utils.communalRepository.setIsCommunalEnabled(true)
+ kosmos.fakeCommunalRepository.setIsCommunalEnabled(true)
val underTest = createLockscreenSceneViewModel()
assertThat(underTest.leftDestinationSceneKey).isEqualTo(SceneKey.Communal)
@@ -78,7 +90,7 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
@Test
fun leftTransitionSceneKey_communalIsDisabled_null() =
testScope.runTest {
- utils.communalRepository.setIsCommunalEnabled(false)
+ kosmos.fakeCommunalRepository.setIsCommunalEnabled(false)
val underTest = createLockscreenSceneViewModel()
assertThat(underTest.leftDestinationSceneKey).isNull()
@@ -87,13 +99,13 @@ class LockscreenSceneViewModelTest : SysuiTestCase() {
private fun createLockscreenSceneViewModel(): LockscreenSceneViewModel {
return LockscreenSceneViewModel(
applicationScope = testScope.backgroundScope,
- deviceEntryInteractor = utils.deviceEntryInteractor(),
- communalInteractor = utils.communalInteractor(),
+ deviceEntryInteractor = kosmos.deviceEntryInteractor,
+ communalInteractor = kosmos.communalInteractor,
longPress =
KeyguardLongPressViewModel(
interactor = mock(),
),
- notifications = utils.notificationsPlaceholderViewModel(),
+ notifications = kosmos.notificationsPlaceholderViewModel,
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index 6403ed19a9b2..be523b813494 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -22,13 +22,15 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.testScope
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Direction
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.scene.shared.model.UserAction
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
@@ -36,6 +38,7 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobi
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
@@ -47,9 +50,9 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class QuickSettingsSceneViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val sceneInteractor = utils.sceneInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
private val qsFlexiglassAdapter = FakeQSSceneAdapter { mock() }
@@ -90,7 +93,7 @@ class QuickSettingsSceneViewModelTest : SysuiTestCase() {
QuickSettingsSceneViewModel(
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
- notifications = utils.notificationsPlaceholderViewModel(),
+ notifications = kosmos.notificationsPlaceholderViewModel,
)
}
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 ecc2ef15dec8..1cd764e01bad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -27,22 +27,38 @@ import com.android.internal.util.EmergencyAffordanceManager
import com.android.internal.util.emergencyAffordanceManager
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
+import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
+import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.communal.domain.interactor.communalInteractor
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.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.model.SysUiState
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.qs.ui.adapter.FakeQSSceneAdapter
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.startable.SceneContainerStartable
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
@@ -50,13 +66,17 @@ import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
import com.android.systemui.settings.FakeDisplayTracker
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -101,13 +121,13 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
class SceneFrameworkIntegrationTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this).apply { fakeSceneContainerFlags.enabled = true }
- private val testScope = utils.testScope
- private val sceneContainerConfig = utils.fakeSceneContainerConfig()
- private val sceneInteractor = utils.sceneInteractor()
- private val authenticationInteractor = utils.authenticationInteractor()
- private val deviceEntryInteractor = utils.deviceEntryInteractor()
- private val communalInteractor = utils.communalInteractor()
+ private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ private val testScope = kosmos.testScope
+ private val sceneContainerConfig = kosmos.sceneContainerConfig
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val authenticationInteractor = kosmos.authenticationInteractor
+ private val deviceEntryInteractor = kosmos.deviceEntryInteractor
+ private val communalInteractor = kosmos.communalInteractor
private val transitionState =
MutableStateFlow<ObservableTransitionState>(
@@ -116,11 +136,11 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
private val sceneContainerViewModel =
SceneContainerViewModel(
sceneInteractor = sceneInteractor,
- falsingInteractor = utils.falsingInteractor(),
+ falsingInteractor = kosmos.falsingInteractor,
)
.apply { setTransitionState(transitionState) }
- private val bouncerInteractor = utils.bouncerInteractor()
+ private val bouncerInteractor = kosmos.bouncerInteractor
private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor
@@ -135,7 +155,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
KeyguardLongPressViewModel(
interactor = mock(),
),
- notifications = utils.notificationsPlaceholderViewModel(),
+ notifications = kosmos.notificationsPlaceholderViewModel,
)
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
@@ -152,22 +172,21 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
FakeMobileConnectionsRepository(),
),
constants = mock(),
- utils.fakeFeatureFlags,
+ flags = kosmos.fakeFeatureFlagsClassic,
scope = testScope.backgroundScope,
)
private lateinit var shadeHeaderViewModel: ShadeHeaderViewModel
private lateinit var shadeSceneViewModel: ShadeSceneViewModel
- private val keyguardInteractor = utils.keyguardInteractor()
- private val powerInteractor = utils.powerInteractor()
+ private val keyguardInteractor = kosmos.keyguardInteractor
+ private val powerInteractor = kosmos.powerInteractor
private var bouncerSceneJob: Job? = null
private val qsFlexiglassAdapter = FakeQSSceneAdapter(inflateDelegate = { mock() })
@Mock private lateinit var mediaDataManager: MediaDataManager
- @Mock private lateinit var mediaHost: MediaHost
private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager
private lateinit var telecomManager: TelecomManager
@@ -177,27 +196,27 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
overrideResource(R.bool.config_enable_emergency_call_while_sim_locked, true)
- telecomManager = checkNotNull(utils.kosmos.telecomManager)
+ telecomManager = checkNotNull(kosmos.telecomManager)
whenever(telecomManager.isInCall).thenReturn(false)
- emergencyAffordanceManager = utils.kosmos.emergencyAffordanceManager
+ emergencyAffordanceManager = kosmos.emergencyAffordanceManager
whenever(emergencyAffordanceManager.needsEmergencyAffordance()).thenReturn(true)
- utils.fakeFeatureFlags.apply {
+ kosmos.fakeFeatureFlagsClassic.apply {
set(Flags.NEW_NETWORK_SLICE_UI, false)
set(Flags.REFACTOR_GETCURRENTUSER, true)
}
- mobileConnectionsRepository = utils.mobileConnectionsRepository
+ mobileConnectionsRepository = kosmos.fakeMobileConnectionsRepository
mobileConnectionsRepository.isAnySimSecure.value = false
- utils.telephonyRepository.apply {
+ kosmos.fakeTelephonyRepository.apply {
setHasTelephonyRadio(true)
setCallState(TelephonyManager.CALL_STATE_IDLE)
setIsInCall(false)
}
- bouncerActionButtonInteractor = utils.bouncerActionButtonInteractor()
- bouncerViewModel = utils.bouncerViewModel()
+ bouncerActionButtonInteractor = kosmos.bouncerActionButtonInteractor
+ bouncerViewModel = kosmos.bouncerViewModel
shadeHeaderViewModel =
ShadeHeaderViewModel(
@@ -215,12 +234,11 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
deviceEntryInteractor = deviceEntryInteractor,
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
- notifications = utils.notificationsPlaceholderViewModel(),
+ notifications = kosmos.notificationsPlaceholderViewModel,
mediaDataManager = mediaDataManager,
- mediaHost = mediaHost,
)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
val displayTracker = FakeDisplayTracker(context)
val sysUiState = SysUiState(displayTracker)
@@ -230,15 +248,15 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
sceneInteractor = sceneInteractor,
deviceEntryInteractor = deviceEntryInteractor,
keyguardInteractor = keyguardInteractor,
- flags = utils.fakeSceneContainerFlags,
+ flags = kosmos.fakeSceneContainerFlags,
sysUiState = sysUiState,
displayId = displayTracker.defaultDisplayId,
sceneLogger = mock(),
- falsingCollector = utils.falsingCollector(),
+ falsingCollector = kosmos.falsingCollector,
powerInteractor = powerInteractor,
bouncerInteractor = bouncerInteractor,
- simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
- authenticationInteractor = dagger.Lazy { utils.authenticationInteractor() },
+ simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
+ authenticationInteractor = dagger.Lazy { kosmos.authenticationInteractor },
windowController = mock(),
)
startable.start()
@@ -534,15 +552,15 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
// Set the lockscreen enabled bit _before_ set the auth method as the code picks up on the
// lockscreen enabled bit _after_ the auth method is changed and the lockscreen enabled bit
// is not an observable that can trigger a new evaluation.
- utils.deviceEntryRepository.setLockscreenEnabled(enableLockscreen)
- utils.authenticationRepository.setAuthenticationMethod(authMethod)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(enableLockscreen)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authMethod)
runCurrent()
}
/** Emulates a phone call in progress. */
private fun TestScope.startPhoneCall() {
whenever(telecomManager.isInCall).thenReturn(true)
- utils.telephonyRepository.apply {
+ kosmos.fakeTelephonyRepository.apply {
setHasTelephonyRadio(true)
setIsInCall(true)
setCallState(TelephonyManager.CALL_STATE_OFFHOOK)
@@ -651,7 +669,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
.that(authMethod.isSecure)
.isTrue()
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
}
@@ -665,7 +683,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
enterPin()
// This repository state is not changed by the AuthInteractor, it relies on
// KeyguardStateController.
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
emulateUiSceneTransition(
expectedVisible = false,
)
@@ -721,7 +739,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
}
pinBouncerViewModel.onAuthenticateButtonClicked()
setAuthMethod(authMethodAfterSimUnlock)
- utils.mobileConnectionsRepository.isAnySimSecure.value = false
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false
runCurrent()
}
@@ -768,7 +786,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() {
private fun TestScope.introduceLockedSim() {
setAuthMethod(AuthenticationMethodModel.Sim)
- utils.mobileConnectionsRepository.isAnySimSecure.value = true
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
runCurrent()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 339d0263a089..b267720971cd 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -22,11 +22,14 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.sceneContainerConfig
import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -39,12 +42,12 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SceneContainerRepositoryTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this).apply { fakeSceneContainerFlags.enabled = true }
- private val testScope = utils.testScope
+ private val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true }
+ private val testScope = kosmos.testScope
@Test
fun allSceneKeys() {
- val underTest = utils.fakeSceneContainerRepository()
+ val underTest = kosmos.sceneContainerRepository
assertThat(underTest.allSceneKeys())
.isEqualTo(
listOf(
@@ -61,7 +64,7 @@ class SceneContainerRepositoryTest : SysuiTestCase() {
@Test
fun desiredScene() =
testScope.runTest {
- val underTest = utils.fakeSceneContainerRepository()
+ val underTest = kosmos.sceneContainerRepository
val currentScene by collectLastValue(underTest.desiredScene)
assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
@@ -71,15 +74,15 @@ class SceneContainerRepositoryTest : SysuiTestCase() {
@Test(expected = IllegalStateException::class)
fun setDesiredScene_noSuchSceneInContainer_throws() {
- utils.kosmos.sceneKeys = listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
- val underTest = utils.fakeSceneContainerRepository()
+ kosmos.sceneKeys = listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)
+ val underTest = kosmos.sceneContainerRepository
underTest.setDesiredScene(SceneModel(SceneKey.Shade))
}
@Test
fun isVisible() =
testScope.runTest {
- val underTest = utils.fakeSceneContainerRepository()
+ val underTest = kosmos.sceneContainerRepository
val isVisible by collectLastValue(underTest.isVisible)
assertThat(isVisible).isTrue()
@@ -93,19 +96,19 @@ class SceneContainerRepositoryTest : SysuiTestCase() {
@Test
fun transitionState_defaultsToIdle() =
testScope.runTest {
- val underTest = utils.fakeSceneContainerRepository()
+ val underTest = kosmos.sceneContainerRepository
val transitionState by collectLastValue(underTest.transitionState)
assertThat(transitionState)
.isEqualTo(
- ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+ ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
)
}
@Test
fun transitionState_reflectsUpdates() =
testScope.runTest {
- val underTest = utils.fakeSceneContainerRepository()
+ val underTest = kosmos.sceneContainerRepository
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(SceneKey.Lockscreen)
@@ -134,7 +137,7 @@ class SceneContainerRepositoryTest : SysuiTestCase() {
underTest.setTransitionState(null)
assertThat(reflectedTransitionState)
.isEqualTo(
- ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+ ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 486f7ab6501d..d159986015a0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -20,10 +20,16 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.scene.data.repository.sceneContainerRepository
+import com.android.systemui.scene.sceneContainerConfig
+import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
@@ -36,20 +42,20 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SceneInteractorTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var underTest: SceneInteractor
@Before
fun setUp() {
- utils.fakeSceneContainerFlags.enabled = true
- underTest = utils.sceneInteractor()
+ kosmos.fakeSceneContainerFlags.enabled = true
+ underTest = kosmos.sceneInteractor
}
@Test
fun allSceneKeys() {
- assertThat(underTest.allSceneKeys()).isEqualTo(utils.fakeSceneKeys())
+ assertThat(underTest.allSceneKeys()).isEqualTo(kosmos.sceneKeys)
}
@Test
@@ -75,7 +81,7 @@ class SceneInteractorTest : SysuiTestCase() {
@Test
fun transitionState() =
testScope.runTest {
- val underTest = utils.fakeSceneContainerRepository()
+ val underTest = kosmos.sceneContainerRepository
val transitionState =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(SceneKey.Lockscreen)
@@ -104,7 +110,7 @@ class SceneInteractorTest : SysuiTestCase() {
underTest.setTransitionState(null)
assertThat(reflectedTransitionState)
.isEqualTo(
- ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+ ObservableTransitionState.Idle(kosmos.sceneContainerConfig.initialSceneKey)
)
}
@@ -348,8 +354,8 @@ class SceneInteractorTest : SysuiTestCase() {
@Test
fun userInput() =
testScope.runTest {
- assertThat(utils.powerRepository.userTouchRegistered).isFalse()
+ assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse()
underTest.onUserInput()
- assertThat(utils.powerRepository.userTouchRegistered).isTrue()
+ assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
index 8be4eeb7be7a..f23716ccca54 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractorTest.kt
@@ -16,6 +16,8 @@
package com.android.systemui.scene.domain.interactor
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.statusbar.IStatusBarService
@@ -28,7 +30,11 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.init.NotificationsController
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -37,6 +43,7 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -50,6 +57,7 @@ import org.mockito.Mockito.verify
class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
private val testScope = TestScope()
+ private val testDispatcher = StandardTestDispatcher()
private val iStatusBarService = mock<IStatusBarService>()
private val executor = FakeExecutor(FakeSystemClock())
private val windowRootViewVisibilityRepository =
@@ -59,6 +67,9 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
private val notificationPresenter = mock<NotificationPresenter>()
private val notificationsController = mock<NotificationsController>()
private val powerInteractor = PowerInteractorFactory.create().powerInteractor
+ private val activeNotificationsRepository = ActiveNotificationListRepository()
+ private val activeNotificationsInteractor =
+ ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher)
private val underTest =
WindowRootViewVisibilityInteractor(
@@ -67,6 +78,7 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
keyguardRepository,
headsUpManager,
powerInteractor,
+ activeNotificationsInteractor,
)
.apply { setUp(notificationPresenter, notificationsController) }
@@ -257,7 +269,8 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
}
@Test
- fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_notifCountOne() =
+ @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+ fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_flagOff_notifCountOne() =
testScope.runTest {
underTest.start()
@@ -273,6 +286,23 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+ fun lockscreenShadeInteractive_hasHeadsUpAndNotifPresenterCollapsed_flagOn_notifCountOne() =
+ testScope.runTest {
+ underTest.start()
+
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+ activeNotificationsRepository.setActiveNotifs(4)
+
+ makeLockscreenShadeVisible()
+
+ val notifCount = argumentCaptor<Int>()
+ verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+ assertThat(notifCount.value).isEqualTo(1)
+ }
+
+ @Test
fun lockscreenShadeInteractive_hasHeadsUpAndNullPresenter_notifCountOne() =
testScope.runTest {
underTest.start()
@@ -288,7 +318,8 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
}
@Test
- fun lockscreenShadeInteractive_noHeadsUp_notifCountMatchesNotifController() =
+ @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+ fun lockscreenShadeInteractive_noHeadsUp_flagOff_notifCountMatchesNotifController() =
testScope.runTest {
underTest.start()
whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
@@ -304,7 +335,25 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
}
@Test
- fun lockscreenShadeInteractive_notifPresenterNotCollapsed_notifCountMatchesNotifController() =
+ @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+ fun lockscreenShadeInteractive_noHeadsUp_flagOn_notifCountMatchesNotifController() =
+ testScope.runTest {
+ underTest.start()
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(true)
+
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(false)
+ activeNotificationsRepository.setActiveNotifs(9)
+
+ makeLockscreenShadeVisible()
+
+ val notifCount = argumentCaptor<Int>()
+ verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+ assertThat(notifCount.value).isEqualTo(9)
+ }
+
+ @Test
+ @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+ fun lockscreenShadeInteractive_notifPresenterNotCollapsed_flagOff_notifCountMatchesNotifController() =
testScope.runTest {
underTest.start()
whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
@@ -320,6 +369,23 @@ class WindowRootViewVisibilityInteractorTest : SysuiTestCase() {
}
@Test
+ @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME)
+ fun lockscreenShadeInteractive_notifPresenterNotCollapsed_flagOn_notifCountMatchesNotifController() =
+ testScope.runTest {
+ underTest.start()
+ whenever(headsUpManager.hasPinnedHeadsUp()).thenReturn(true)
+
+ whenever(notificationPresenter.isPresenterFullyCollapsed).thenReturn(false)
+ activeNotificationsRepository.setActiveNotifs(8)
+
+ makeLockscreenShadeVisible()
+
+ val notifCount = argumentCaptor<Int>()
+ verify(iStatusBarService).onPanelRevealed(any(), notifCount.capture())
+ assertThat(notifCount.value).isEqualTo(8)
+ }
+
+ @Test
fun lockscreenShadeInteractive_noHeadsUp_noNotifController_notifCountZero() =
testScope.runTest {
underTest.start()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 5fe4ca1fbfe3..4afa5f2a44b9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -25,19 +25,31 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags as AconfigFlags
import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
+import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.classifier.FalsingCollector
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.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.testScope
import com.android.systemui.model.SysUiState
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.PowerInteractorFactory
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -65,15 +77,15 @@ class SceneContainerStartableTest : SysuiTestCase() {
@Mock private lateinit var windowController: NotificationShadeWindowController
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val sceneInteractor = utils.sceneInteractor()
- private val sceneContainerFlags = utils.fakeSceneContainerFlags
- private val authenticationInteractor = utils.authenticationInteractor()
- private val bouncerInteractor = utils.bouncerInteractor()
- private val faceAuthRepository = utils.kosmos.fakeDeviceEntryFaceAuthRepository
- private val deviceEntryInteractor = utils.deviceEntryInteractor()
- private val keyguardInteractor = utils.keyguardInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val sceneContainerFlags = kosmos.fakeSceneContainerFlags
+ private val authenticationInteractor = kosmos.authenticationInteractor
+ private val bouncerInteractor = kosmos.bouncerInteractor
+ private val faceAuthRepository = kosmos.fakeDeviceEntryFaceAuthRepository
+ private val deviceEntryInteractor = kosmos.deviceEntryInteractor
+ private val keyguardInteractor = kosmos.keyguardInteractor
private val sysUiState: SysUiState = mock()
private val falsingCollector: FalsingCollector = mock()
private val powerInteractor = PowerInteractorFactory.create().powerInteractor
@@ -97,7 +109,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
falsingCollector = falsingCollector,
powerInteractor = powerInteractor,
bouncerInteractor = bouncerInteractor,
- simBouncerInteractor = dagger.Lazy { utils.simBouncerInteractor },
+ simBouncerInteractor = dagger.Lazy { kosmos.simBouncerInteractor },
authenticationInteractor = dagger.Lazy { authenticationInteractor },
windowController = windowController,
)
@@ -172,7 +184,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
underTest.start()
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -188,7 +200,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
underTest.start()
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -204,7 +216,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -222,7 +234,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
// Authenticate using a passive auth method like face auth while bypass is disabled.
faceAuthRepository.isAuthenticated.value = true
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -244,7 +256,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
transitionStateFlowValue.value = ObservableTransitionState.Idle(SceneKey.Shade)
assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
runCurrent()
assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
@@ -263,7 +275,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
// Authenticate using a passive auth method like face auth while bypass is disabled.
faceAuthRepository.isAuthenticated.value = true
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -373,7 +385,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
underTest.start()
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
runCurrent()
powerInteractor.setAwakeForTest()
runCurrent()
@@ -463,11 +475,11 @@ class SceneContainerStartableTest : SysuiTestCase() {
runCurrent()
verify(falsingCollector).setShowingAod(false)
- utils.keyguardRepository.setIsDozing(true)
+ kosmos.fakeKeyguardRepository.setIsDozing(true)
runCurrent()
verify(falsingCollector).setShowingAod(true)
- utils.keyguardRepository.setIsDozing(false)
+ kosmos.fakeKeyguardRepository.setIsDozing(false)
runCurrent()
verify(falsingCollector, times(2)).setShowingAod(false)
}
@@ -493,7 +505,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
@Test
fun collectFalsingSignals_screenOnAndOff_aodUnavailable() =
testScope.runTest {
- utils.keyguardRepository.setAodAvailable(false)
+ kosmos.fakeKeyguardRepository.setAodAvailable(false)
runCurrent()
prepareState(
initialSceneKey = SceneKey.Lockscreen,
@@ -541,7 +553,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
@Test
fun collectFalsingSignals_screenOnAndOff_aodAvailable() =
testScope.runTest {
- utils.keyguardRepository.setAodAvailable(true)
+ kosmos.fakeKeyguardRepository.setAodAvailable(true)
runCurrent()
prepareState(
initialSceneKey = SceneKey.Lockscreen,
@@ -619,7 +631,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
underTest.start()
runCurrent()
- utils.mobileConnectionsRepository.isAnySimSecure.value = true
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
runCurrent()
assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
@@ -628,7 +640,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
@Test
fun switchesToLockscreen_whenSimBecomesUnlocked() =
testScope.runTest {
- utils.mobileConnectionsRepository.isAnySimSecure.value = true
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
@@ -638,7 +650,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
)
underTest.start()
runCurrent()
- utils.mobileConnectionsRepository.isAnySimSecure.value = false
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false
runCurrent()
assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
@@ -647,7 +659,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
@Test
fun switchesToGone_whenSimBecomesUnlocked_ifDeviceUnlockedAndLockscreenDisabled() =
testScope.runTest {
- utils.mobileConnectionsRepository.isAnySimSecure.value = true
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = true
val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
prepareState(
@@ -658,7 +670,7 @@ class SceneContainerStartableTest : SysuiTestCase() {
)
underTest.start()
runCurrent()
- utils.mobileConnectionsRepository.isAnySimSecure.value = false
+ kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false
runCurrent()
assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
@@ -730,8 +742,8 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
}
sceneContainerFlags.enabled = true
- utils.deviceEntryRepository.setUnlocked(isDeviceUnlocked)
- utils.deviceEntryRepository.setBypassEnabled(isBypassEnabled)
+ kosmos.fakeDeviceEntryRepository.setUnlocked(isDeviceUnlocked)
+ kosmos.fakeDeviceEntryRepository.setBypassEnabled(isBypassEnabled)
val transitionStateFlow =
MutableStateFlow<ObservableTransitionState>(
ObservableTransitionState.Idle(SceneKey.Lockscreen)
@@ -743,8 +755,8 @@ class SceneContainerStartableTest : SysuiTestCase() {
sceneInteractor.onSceneChanged(SceneModel(it), "reason")
}
authenticationMethod?.let {
- utils.authenticationRepository.setAuthenticationMethod(authenticationMethod)
- utils.deviceEntryRepository.setLockscreenEnabled(
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authenticationMethod)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(
isLockscreenEnabled = isLockscreenEnabled
)
}
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 3a4ee642685f..ede453d85ee1 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,10 +21,14 @@ package com.android.systemui.scene.ui.viewmodel
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.domain.interactor.falsingInteractor
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.sceneKeys
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
@@ -36,17 +40,17 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class SceneContainerViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val interactor = utils.sceneInteractor()
+ private val kosmos = testKosmos()
+ private val interactor = kosmos.sceneInteractor
private lateinit var underTest: SceneContainerViewModel
@Before
fun setUp() {
- utils.fakeSceneContainerFlags.enabled = true
+ kosmos.fakeSceneContainerFlags.enabled = true
underTest =
SceneContainerViewModel(
sceneInteractor = interactor,
- falsingInteractor = utils.falsingInteractor(),
+ falsingInteractor = kosmos.falsingInteractor,
)
}
@@ -64,7 +68,7 @@ class SceneContainerViewModelTest : SysuiTestCase() {
@Test
fun allSceneKeys() {
- assertThat(underTest.allSceneKeys).isEqualTo(utils.fakeSceneKeys())
+ assertThat(underTest.allSceneKeys).isEqualTo(kosmos.sceneKeys)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
index 77ddf15c41ad..51745023c5be 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModelTest.kt
@@ -7,7 +7,8 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.ObservableTransitionState
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
@@ -18,6 +19,7 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobi
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
@@ -31,9 +33,9 @@ import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidJUnit4::class)
class ShadeHeaderViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val sceneInteractor = utils.sceneInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index a8133a3a4de3..e9801652f060 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -19,16 +19,20 @@ package com.android.systemui.shade.ui.viewmodel
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.shared.model.AuthenticationMethodModel
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.flags.FakeFeatureFlagsClassic
import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
@@ -36,6 +40,7 @@ import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobi
import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
@@ -53,10 +58,10 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
class ShadeSceneViewModelTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val sceneInteractor = utils.sceneInteractor()
- private val deviceEntryInteractor = utils.deviceEntryInteractor()
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val sceneInteractor = kosmos.sceneInteractor
+ private val deviceEntryInteractor = kosmos.deviceEntryInteractor
private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
private val flags = FakeFeatureFlagsClassic().also { it.set(Flags.NEW_NETWORK_SLICE_UI, false) }
@@ -84,7 +89,6 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
private lateinit var underTest: ShadeSceneViewModel
@Mock private lateinit var mediaDataManager: MediaDataManager
- @Mock private lateinit var mediaHost: MediaHost
@Before
fun setUp() {
@@ -105,9 +109,8 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
deviceEntryInteractor = deviceEntryInteractor,
shadeHeaderViewModel = shadeHeaderViewModel,
qsSceneAdapter = qsFlexiglassAdapter,
- notifications = utils.notificationsPlaceholderViewModel(),
+ notifications = kosmos.notificationsPlaceholderViewModel,
mediaDataManager = mediaDataManager,
- mediaHost = mediaHost,
)
}
@@ -115,8 +118,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
fun upTransitionSceneKey_deviceLocked_lockScreen() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
}
@@ -125,8 +130,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
fun upTransitionSceneKey_deviceUnlocked_gone() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
}
@@ -135,8 +142,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
@@ -147,8 +156,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
testScope.runTest {
val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
- utils.deviceEntryRepository.setLockscreenEnabled(true)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+ kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
@@ -159,8 +170,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
fun onContentClicked_deviceUnlocked_switchesToGone() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(true)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(true)
runCurrent()
underTest.onContentClicked()
@@ -172,8 +185,10 @@ class ShadeSceneViewModelTest : SysuiTestCase() {
fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
testScope.runTest {
val currentScene by collectLastValue(sceneInteractor.desiredScene)
- utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
- utils.deviceEntryRepository.setUnlocked(false)
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.Pin
+ )
+ kosmos.fakeDeviceEntryRepository.setUnlocked(false)
runCurrent()
underTest.onContentClicked()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
index 262795fe0eb6..8e8e5106e93c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/telephony/data/repository/TelephonyRepositoryImplTest.kt
@@ -24,12 +24,13 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.telephony.TelephonyListenerManager
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.kotlinArgumentCaptor
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -39,7 +40,6 @@ import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class TelephonyRepositoryImplTest : SysuiTestCase() {
@@ -47,8 +47,8 @@ class TelephonyRepositoryImplTest : SysuiTestCase() {
@Mock private lateinit var manager: TelephonyListenerManager
@Mock private lateinit var telecomManager: TelecomManager
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var underTest: TelephonyRepositoryImpl
@@ -61,7 +61,7 @@ class TelephonyRepositoryImplTest : SysuiTestCase() {
TelephonyRepositoryImpl(
applicationScope = testScope.backgroundScope,
applicationContext = context,
- backgroundDispatcher = utils.testDispatcher,
+ backgroundDispatcher = kosmos.testDispatcher,
manager = manager,
telecomManager = telecomManager,
)
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml
new file mode 100644
index 000000000000..84b89ca68e65
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true">
+ <shape android:shape="rectangle">
+ <corners android:radius="16dp" />
+ <!--By default this outline will not show hence 0 width.
+ width is set programmatically when needed and is gated by the flag:
+ com.android.systemui.Flags.pinInputFieldStyledFocusState-->
+ <stroke android:width="0dp"
+ android:color="@color/bouncer_password_focus_color" />
+ </shape>
+ </item>
+</selector> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
index 66c54f2a668e..0b35559148af 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
@@ -23,7 +23,7 @@
android:layout_marginTop="@dimen/keyguard_lock_padding"
android:importantForAccessibility="no"
android:ellipsize="marquee"
- android:focusable="true"
+ android:focusable="false"
android:gravity="center"
android:singleLine="true" />
</merge>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 0628c3e957b1..ddad1e3f8940 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -25,6 +25,12 @@
<!-- Maximum width of the sliding KeyguardSecurityContainer -->
<dimen name="keyguard_security_width">420dp</dimen>
+ <!-- Width for the keyguard pin input field -->
+ <dimen name="keyguard_pin_field_width">292dp</dimen>
+
+ <!-- Width for the keyguard pin input field -->
+ <dimen name="keyguard_pin_field_height">48dp</dimen>
+
<!-- Height of the sliding KeyguardSecurityContainer
(includes 2x keyguard_security_view_top_margin) -->
<dimen name="keyguard_security_height">420dp</dimen>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 565ed1085fb9..f51e1098f333 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -62,10 +62,10 @@
<string name="keyguard_plugged_in_charging_slowly"><xliff:g id="percentage">%s</xliff:g> • Charging slowly</string>
<!-- When the lock screen is showing and the phone plugged in, and the defend mode is triggered, say that charging is temporarily limited. -->
- <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging optimized to protect battery</string>
+ <string name="keyguard_plugged_in_charging_limited"><xliff:g id="percentage">%s</xliff:g> • Charging on hold to protect battery</string>
<!-- When the lock screen is showing and the phone plugged in with incompatible charger. -->
- <string name="keyguard_plugged_in_incompatible_charger"><xliff:g id="percentage">%s</xliff:g> • Issue with charging accessory</string>
+ <string name="keyguard_plugged_in_incompatible_charger"><xliff:g id="percentage">%s</xliff:g> • Check charging accessory</string>
<!-- SIM messages --><skip />
<!-- When the user inserts a sim card from an unsupported network, it becomes network locked -->
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 2cca9510417a..4789a229c4d0 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -76,6 +76,7 @@
</style>
<style name="Widget.TextView.Password" parent="@android:style/Widget.TextView">
<item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:background">@drawable/bouncer_password_text_view_focused_background</item>
<item name="android:gravity">center</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml
deleted file mode 100644
index 045c19eb09dc..000000000000
--- a/packages/SystemUI/res/drawable/ic_satellite_connected_0.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
- android:fillAlpha="0.3"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
- android:fillAlpha="0.3"
- android:fillColor="#fff"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml
deleted file mode 100644
index 5e012ab7edbd..000000000000
--- a/packages/SystemUI/res/drawable/ic_satellite_connected_1.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
- android:fillAlpha="0.3"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
- android:fillColor="#fff"/>
-</vector>
-
diff --git a/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml b/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml
deleted file mode 100644
index d8a9a703260d..000000000000
--- a/packages/SystemUI/res/drawable/ic_satellite_connected_2.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,22.48V21.48C11,21.21 11.22,21 11.49,20.99C16.63,20.8 20.75,16.62 20.99,11.48C21,11.21 21.21,11 21.48,11H22.48C22.76,11 23,11.24 22.99,11.52C22.72,17.73 17.73,22.73 11.52,22.99C11.24,23 11,22.77 11,22.48Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M11,18.98V17.98C11,17.71 11.21,17.51 11.48,17.49C14.69,17.26 17.33,14.7 17.49,11.49C17.5,11.22 17.71,11.01 17.98,11.01H18.79C19.26,11.01 19.5,11.25 19.48,11.53C19.22,15.8 15.79,19.23 11.52,19.49C11.24,19.51 11,19.27 11,18.99V18.98Z"
- android:fillColor="#fff"/>
-</vector>
diff --git a/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml b/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
deleted file mode 100644
index dec9930959a0..000000000000
--- a/packages/SystemUI/res/drawable/ic_satellite_not_connected.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0"
- >
- <path
- android:pathData="M14.73,3.36L17.63,6.2C17.83,6.39 17.83,6.71 17.63,6.91L16.89,7.65C16.69,7.85 16.37,7.85 16.18,7.65L13.34,4.78C13.15,4.59 13.15,4.28 13.34,4.08L14.01,3.37C14.2,3.17 14.52,3.16 14.72,3.36H14.73ZM14.37,1C13.85,1 13.32,1.2 12.93,1.61L11.56,3.06C10.8,3.84 10.81,5.09 11.58,5.86L15.13,9.41C15.52,9.8 16.03,10 16.55,10C17.07,10 17.58,9.8 17.97,9.41L19.42,7.96C20.21,7.17 20.2,5.89 19.4,5.12L15.77,1.57C15.38,1.19 14.88,1 14.37,1Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M4.73,13.36L7.63,16.2C7.83,16.39 7.83,16.71 7.63,16.91L6.89,17.65C6.69,17.85 6.37,17.85 6.18,17.65L3.34,14.78C3.15,14.59 3.15,14.28 3.34,14.08L4.01,13.37C4.2,13.17 4.52,13.16 4.72,13.36H4.73ZM4.37,11C3.85,11 3.32,11.2 2.93,11.61L1.56,13.06C0.8,13.84 0.81,15.09 1.58,15.86L5.13,19.41C5.52,19.8 6.03,20 6.55,20C7.07,20 7.58,19.8 7.97,19.41L9.42,17.96C10.21,17.17 10.2,15.89 9.4,15.12L5.77,11.57C5.38,11.19 4.88,11 4.37,11Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M8.622,5.368L5.372,8.618L10.112,13.358C11.009,14.255 12.464,14.255 13.362,13.358C14.259,12.46 14.259,11.005 13.362,10.108L8.622,5.368Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M16.766,3.169L13.471,6.464L14.532,7.525L17.827,4.23L16.766,3.169Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M14.728,5.226L3.478,16.476L4.538,17.536L15.788,6.286L14.728,5.226Z"
- android:fillColor="#fff"/>
- <path
- android:pathData="M12.63,9.38L9.38,12.63L4.67,7.92C3.77,7.02 3.77,5.57 4.67,4.67C5.57,3.77 7.02,3.77 7.92,4.67L12.63,9.38Z"
- android:fillColor="#fff"/>
-</vector>
diff --git a/packages/SystemUI/res/layout/bindable_status_bar_icon.xml b/packages/SystemUI/res/layout/bindable_status_bar_icon.xml
deleted file mode 100644
index ee4d05c3bda5..000000000000
--- a/packages/SystemUI/res/layout/bindable_status_bar_icon.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ Copyright (C) 2023 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-
-<!-- Base layout that provides a single bindable icon_view id image view -->
-<com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center_vertical"
- >
-
- <ImageView
- android:id="@+id/icon_view"
- android:layout_height="@dimen/status_bar_bindable_icon_size"
- android:layout_width="wrap_content"
- android:layout_gravity="center_vertical"
- android:padding="@dimen/status_bar_bindable_icon_padding"
- android:scaleType="fitCenter"
- />
-
-</com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView>
diff --git a/packages/SystemUI/res/layout/biometric_prompt_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
index 23fbb12f3036..10f71134c4cc 100644
--- a/packages/SystemUI/res/layout/biometric_prompt_layout.xml
+++ b/packages/SystemUI/res/layout/biometric_prompt_layout.xml
@@ -20,6 +20,13 @@
android:layout_height="wrap_content"
android:orientation="vertical">
+ <ImageView
+ android:id="@+id/logo"
+ android:layout_width="@dimen/biometric_auth_icon_size"
+ android:layout_height="@dimen/biometric_auth_icon_size"
+ android:layout_gravity="center"
+ android:scaleType="fitXY"/>
+
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
index 0534c6ee466f..a0f916c6827a 100644
--- a/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
+++ b/packages/SystemUI/res/layout/bluetooth_tile_dialog.xml
@@ -17,6 +17,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/root"
style="@style/Widget.SliceView.Panel"
android:layout_width="wrap_content"
@@ -53,17 +54,40 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_title" />
+ <View
+ android:id="@+id/bluetooth_tile_dialog_progress_background"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constraintEnd_toEndOf="@id/bluetooth_tile_dialog_progress_animation"
+ app:layout_constraintStart_toStartOf="@id/bluetooth_tile_dialog_progress_animation"
+ app:layout_constraintTop_toTopOf="@id/bluetooth_tile_dialog_progress_animation"
+ app:layout_constraintBottom_toBottomOf="@id/bluetooth_tile_dialog_progress_animation"
+ android:background="?androidprv:attr/colorSurfaceVariant" />
+
+ <ProgressBar
+ android:id="@+id/bluetooth_tile_dialog_progress_animation"
+ android:layout_width="152dp"
+ android:layout_height="4dp"
+ android:layout_marginTop="16dp"
+ style="@style/TrimmedHorizontalProgressBar"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle"
+ android:visibility="invisible"
+ android:indeterminate="true" />
+
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="21dp"
+ android:minHeight="145dp"
android:fillViewport="true"
app:layout_constrainedHeight="true"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_subtitle">
+ app:layout_constraintTop_toBottomOf="@id/bluetooth_tile_dialog_progress_animation">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/scroll_layout"
@@ -81,7 +105,7 @@
android:paddingStart="36dp"
android:text="@string/turn_on_bluetooth"
android:clickable="false"
- android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+ android:textAppearance="@style/TextAppearance.Dialog.Title"
android:textSize="16sp"
app:layout_constraintEnd_toStartOf="@+id/bluetooth_toggle"
app:layout_constraintStart_toStartOf="parent"
@@ -90,8 +114,7 @@
<Switch
android:id="@+id/bluetooth_toggle"
android:layout_width="wrap_content"
- android:layout_height="48dp"
- android:paddingTop="10dp"
+ android:layout_height="64dp"
android:gravity="start|center_vertical"
android:paddingEnd="40dp"
android:contentDescription="@string/turn_on_bluetooth"
@@ -107,7 +130,6 @@
android:id="@+id/device_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="20dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/bluetooth_toggle"
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 85b6e8dc12b3..5db9eee6a908 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -50,7 +50,10 @@
app:layout_constraintStart_toStartOf="@id/album_art"
app:layout_constraintEnd_toEndOf="@id/album_art"
app:layout_constraintTop_toTopOf="@id/album_art"
- app:layout_constraintBottom_toBottomOf="@id/album_art" />
+ app:layout_constraintBottom_toBottomOf="@id/album_art"
+ android:clipToOutline="true"
+ android:background="@drawable/qs_media_outline_layout_bg"
+ />
<com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseView
android:id="@+id/turbulence_noise_view"
@@ -59,7 +62,10 @@
app:layout_constraintStart_toStartOf="@id/album_art"
app:layout_constraintEnd_toEndOf="@id/album_art"
app:layout_constraintTop_toTopOf="@id/album_art"
- app:layout_constraintBottom_toBottomOf="@id/album_art" />
+ app:layout_constraintBottom_toBottomOf="@id/album_art"
+ android:clipToOutline="true"
+ android:background="@drawable/qs_media_outline_layout_bg"
+ />
<!-- Guideline for output switcher -->
<androidx.constraintlayout.widget.Guideline
diff --git a/packages/SystemUI/res/layout/qs_footer_impl.xml b/packages/SystemUI/res/layout/qs_footer_impl.xml
index 73874a08b0bd..b3f32a2863a8 100644
--- a/packages/SystemUI/res/layout/qs_footer_impl.xml
+++ b/packages/SystemUI/res/layout/qs_footer_impl.xml
@@ -19,7 +19,7 @@
<com.android.systemui.qs.QSFooterView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/qs_footer"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
+ android:layout_height="@dimen/qs_footer_height"
android:layout_marginStart="@dimen/qs_footer_margin"
android:layout_marginEnd="@dimen/qs_footer_margin"
android:layout_marginBottom="@dimen/qs_footers_margin_bottom"
@@ -31,7 +31,7 @@
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="@dimen/qs_footer_height"
+ android:layout_height="match_parent"
android:layout_gravity="center_vertical">
<TextView
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index bcc3c83b4560..61a323d44dfc 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -93,6 +93,8 @@
<color name="qs_user_switcher_selected_avatar_icon_color">#202124</color>
<!-- Color of background circle of user avatars in quick settings user switcher -->
<color name="qs_user_switcher_avatar_background">#3C4043</color>
+ <!-- Color of border for keyguard password input when focused -->
+ <color name="bouncer_password_focus_color">@*android:color/system_secondary_dark</color>
<!-- Accessibility floating menu -->
<color name="accessibility_floating_menu_background">#B3000000</color> <!-- 70% -->
diff --git a/packages/SystemUI/res/values-sw720dp-land/config.xml b/packages/SystemUI/res/values-sw720dp-land/config.xml
index be34a48d1cd6..0955f678dfd4 100644
--- a/packages/SystemUI/res/values-sw720dp-land/config.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/config.xml
@@ -24,9 +24,6 @@
<!-- The maximum number of tiles in the QuickQSPanel -->
<integer name="quick_qs_panel_max_tiles">6</integer>
- <!-- Whether to use the split 2-column notification shade -->
- <bool name="config_use_split_notification_shade">true</bool>
-
<!-- The number of columns in the QuickSettings -->
<integer name="quick_settings_num_columns">3</integer>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 8be1cc7282e3..3839dd98cdd3 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -56,6 +56,8 @@
<color name="kg_user_switcher_restricted_avatar_icon_color">@color/GM2_grey_600</color>
<!-- Color of background circle of user avatars in keyguard user switcher -->
<color name="user_avatar_color_bg">?android:attr/colorBackgroundFloating</color>
+ <!-- Color of border for keyguard password input when focused -->
+ <color name="bouncer_password_focus_color">@*android:color/system_secondary_light</color>
<!-- Icon color for user avatars in user switcher quick settings -->
<color name="qs_user_switcher_avatar_icon_color">#3C4043</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9a4520c5fa52..ee2a1ceab2b7 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -151,8 +151,6 @@
<dimen name="status_bar_icon_size_sp">@*android:dimen/status_bar_icon_size_sp</dimen>
<!-- Original dp height of notification icons in the status bar -->
<dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
- <dimen name="status_bar_bindable_icon_size">20sp</dimen>
- <dimen name="status_bar_bindable_icon_padding">2sp</dimen>
<!-- Default horizontal drawable padding for status bar icons. -->
<dimen name="status_bar_horizontal_padding">2.5sp</dimen>
@@ -899,6 +897,10 @@
<dimen name="communal_tutorial_indicator_padding">24dp</dimen>
<dimen name="communal_tutorial_indicator_horizontal_offset">32dp</dimen>
+ <!-- Size of the maximum radius for the enforced rounded rectangles on communal hub.
+ Keep it the same as in Launcher-->
+ <dimen name="communal_enforced_rounded_corner_max_radius">16dp</dimen>
+
<!-- The width/height of the unlock icon view on keyguard. -->
<dimen name="keyguard_lock_height">42dp</dimen>
<dimen name="keyguard_lock_padding">20dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 6e035e8c8c36..ec4c7d5bf67e 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -249,6 +249,9 @@
-->
<item type="id" name="tag_smartspace_view" />
+ <!-- ID of the Scene Container root Composable view -->
+ <item type='id' name="scene_container_root_composable" />
+
<!-- Tag set on the Compose implementation of the QS footer actions. -->
<item type="id" name="tag_compose_qs_footer_actions" />
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index a5a545af641a..033f93b260ab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -3,6 +3,7 @@ package com.android.keyguard;
import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_X_CLOCK_DESIGN;
import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_DESIGN;
import static com.android.keyguard.KeyguardStatusAreaView.TRANSLATE_Y_CLOCK_SIZE;
+import static com.android.systemui.Flags.migrateClocksToBlueprint;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -191,11 +192,11 @@ public class KeyguardClockSwitch extends RelativeLayout {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
-
- mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
- mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
- mStatusArea = findViewById(R.id.keyguard_status_area);
-
+ if (!migrateClocksToBlueprint()) {
+ mSmallClockFrame = findViewById(R.id.lockscreen_clock_view);
+ mLargeClockFrame = findViewById(R.id.lockscreen_clock_view_large);
+ mStatusArea = findViewById(R.id.keyguard_status_area);
+ }
onConfigChanged();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 66f965aea76f..efd8f7f97ca3 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -37,6 +37,7 @@ import com.android.systemui.bouncer.ui.binder.BouncerMessageViewBinder;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
import com.android.systemui.log.BouncerLogger;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.DevicePostureController;
@@ -210,6 +211,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
private final FeatureFlags mFeatureFlags;
private final SelectedUserInteractor mSelectedUserInteractor;
private final UiEventLogger mUiEventLogger;
+ private final KeyboardRepository mKeyboardRepository;
@Inject
public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -223,7 +225,8 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
DevicePostureController devicePostureController,
KeyguardViewController keyguardViewController,
FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ KeyboardRepository keyboardRepository) {
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mLatencyTracker = latencyTracker;
@@ -240,6 +243,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
mFeatureFlags = featureFlags;
mSelectedUserInteractor = selectedUserInteractor;
mUiEventLogger = uiEventLogger;
+ mKeyboardRepository = keyboardRepository;
}
/** Create a new {@link KeyguardInputViewController}. */
@@ -268,19 +272,22 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView>
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, emergencyButtonController, mFalsingCollector,
mDevicePostureController, mFeatureFlags, mSelectedUserInteractor,
- mUiEventLogger);
+ mUiEventLogger, mKeyboardRepository
+ );
} else if (keyguardInputView instanceof KeyguardSimPinView) {
return new KeyguardSimPinViewController((KeyguardSimPinView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
- emergencyButtonController, mFeatureFlags, mSelectedUserInteractor);
+ emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
+ mKeyboardRepository);
} else if (keyguardInputView instanceof KeyguardSimPukView) {
return new KeyguardSimPukViewController((KeyguardSimPukView) keyguardInputView,
mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
mLiftToActivateListener, mTelephonyManager, mFalsingCollector,
- emergencyButtonController, mFeatureFlags, mSelectedUserInteractor);
+ emergencyButtonController, mFeatureFlags, mSelectedUserInteractor,
+ mKeyboardRepository);
}
throw new RuntimeException("Unable to find controller for " + keyguardInputView);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 36fe75f69a45..fcff0dbc0878 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -25,6 +25,7 @@ import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_RESTART_FO
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_TRUSTAGENT_EXPIRED;
import static com.android.keyguard.KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
+import static com.android.systemui.Flags.pinInputFieldStyledFocusState;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -168,7 +169,9 @@ public abstract class KeyguardPinBasedInputView extends KeyguardAbsKeyInputView
// Set selected property on so the view can send accessibility events.
mPasswordEntry.setSelected(true);
- mPasswordEntry.setDefaultFocusHighlightEnabled(false);
+ if (!pinInputFieldStyledFocusState()) {
+ mPasswordEntry.setDefaultFocusHighlightEnabled(false);
+ }
mOkButton = findViewById(R.id.key_enter);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index 376933dc5519..60dd5686c315 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -16,17 +16,25 @@
package com.android.keyguard;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import static com.android.systemui.Flags.pinInputFieldStyledFocusState;
+
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.StateListDrawable;
+import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnKeyListener;
import android.view.View.OnTouchListener;
+import android.view.ViewGroup;
import com.android.internal.util.LatencyTracker;
import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -35,6 +43,7 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
private final LiftToActivateListener mLiftToActivateListener;
private final FalsingCollector mFalsingCollector;
+ private final KeyboardRepository mKeyboardRepository;
protected PasswordTextView mPasswordEntry;
private final OnKeyListener mOnKeyListener = (v, keyCode, event) -> {
@@ -65,12 +74,14 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
EmergencyButtonController emergencyButtonController,
FalsingCollector falsingCollector,
FeatureFlags featureFlags,
- SelectedUserInteractor selectedUserInteractor) {
+ SelectedUserInteractor selectedUserInteractor,
+ KeyboardRepository keyboardRepository) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, falsingCollector,
emergencyButtonController, featureFlags, selectedUserInteractor);
mLiftToActivateListener = liftToActivateListener;
mFalsingCollector = falsingCollector;
+ mKeyboardRepository = keyboardRepository;
mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId());
}
@@ -120,6 +131,35 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
});
okButton.setOnHoverListener(mLiftToActivateListener);
}
+ if (pinInputFieldStyledFocusState()) {
+ collectFlow(mPasswordEntry, mKeyboardRepository.isAnyKeyboardConnected(),
+ this::setKeyboardBasedFocusOutline);
+
+ /**
+ * new UI Specs for PIN Input field have new dimensions go/pin-focus-states.
+ * However we want these changes behind a flag, and resource files cannot be flagged
+ * hence the dimension change in code. When the flags are removed these dimensions
+ * should be set in resources permanently and the code below removed.
+ */
+ ViewGroup.LayoutParams layoutParams = mPasswordEntry.getLayoutParams();
+ layoutParams.width = (int) getResources().getDimension(
+ R.dimen.keyguard_pin_field_width);
+ layoutParams.height = (int) getResources().getDimension(
+ R.dimen.keyguard_pin_field_height);
+ }
+ }
+
+ private void setKeyboardBasedFocusOutline(boolean isAnyKeyboardConnected) {
+ StateListDrawable background = (StateListDrawable) mPasswordEntry.getBackground();
+ GradientDrawable stateDrawable = (GradientDrawable) background.getStateDrawable(0);
+ int color = getResources().getColor(R.color.bouncer_password_focus_color);
+ if (!isAnyKeyboardConnected) {
+ stateDrawable.setStroke(0, color);
+ } else {
+ int strokeWidthInDP = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3,
+ getResources().getDisplayMetrics());
+ stateDrawable.setStroke(strokeWidthInDP, color);
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
index 2aab1f189263..b958f55bdf79 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinViewController.java
@@ -28,6 +28,7 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.policy.DevicePostureController;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -58,12 +59,13 @@ public class KeyguardPinViewController
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
EmergencyButtonController emergencyButtonController,
FalsingCollector falsingCollector,
- DevicePostureController postureController,
- FeatureFlags featureFlags, SelectedUserInteractor selectedUserInteractor,
- UiEventLogger uiEventLogger) {
+ DevicePostureController postureController, FeatureFlags featureFlags,
+ SelectedUserInteractor selectedUserInteractor, UiEventLogger uiEventLogger,
+ KeyboardRepository keyboardRepository) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
- emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
+ emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
+ keyboardRepository);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mPostureController = postureController;
mLockPatternUtils = lockPatternUtils;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 5729119a582a..1cdcbd06815f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -44,6 +44,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -93,10 +94,11 @@ public class KeyguardSimPinViewController
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
- SelectedUserInteractor selectedUserInteractor) {
+ SelectedUserInteractor selectedUserInteractor, KeyboardRepository keyboardRepository) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
- emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
+ emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
+ keyboardRepository);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
index 05fb5fa75e9e..f019d61a3ccc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukViewController.java
@@ -39,6 +39,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyboard.data.repository.KeyboardRepository;
import com.android.systemui.res.R;
import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
@@ -90,10 +91,11 @@ public class KeyguardSimPukViewController
LatencyTracker latencyTracker, LiftToActivateListener liftToActivateListener,
TelephonyManager telephonyManager, FalsingCollector falsingCollector,
EmergencyButtonController emergencyButtonController, FeatureFlags featureFlags,
- SelectedUserInteractor selectedUserInteractor) {
+ SelectedUserInteractor selectedUserInteractor, KeyboardRepository keyboardRepository) {
super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback,
messageAreaControllerFactory, latencyTracker, liftToActivateListener,
- emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor);
+ emergencyButtonController, falsingCollector, featureFlags, selectedUserInteractor,
+ keyboardRepository);
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mTelephonyManager = telephonyManager;
mSimImageView = mView.findViewById(R.id.keyguard_sim);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index d372f5a616b1..1758831203d6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -494,7 +494,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
boolean shouldBeCentered,
boolean animate) {
if (migrateClocksToBlueprint()) {
- mKeyguardInteractor.setClockShouldBeCentered(mSplitShadeEnabled && shouldBeCentered);
+ mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered);
} else {
mKeyguardClockSwitchController.setSplitShadeCentered(
splitShadeEnabled && shouldBeCentered);
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
index b15aaaf9a00e..e88aaf015f87 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
@@ -91,7 +91,7 @@ abstract class SystemUIAppComponentFactoryBase : AppComponentFactory() {
return app
}
- @UsesReflection(KeepTarget(extendsClassConstant = SysUIComponent::class, methodName = "inject"))
+ @UsesReflection(KeepTarget(instanceOfClassConstant = SysUIComponent::class, methodName = "inject"))
override fun instantiateProviderCompat(cl: ClassLoader, className: String): ContentProvider {
val contentProvider = super.instantiateProviderCompat(cl, className)
if (contentProvider is ContextInitializer) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
index 6483ae44d5ec..c7e5b645db5f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
@@ -19,16 +19,20 @@ package com.android.systemui.accessibility.data.repository
import android.os.UserHandle
import android.provider.Settings.Secure
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.withContext
/** Provides data related to color correction. */
@@ -45,22 +49,24 @@ class ColorCorrectionRepositoryImpl
@Inject
constructor(
@Background private val bgCoroutineContext: CoroutineContext,
+ @Application private val scope: CoroutineScope,
private val secureSettings: SecureSettings,
) : ColorCorrectionRepository {
- companion object {
- const val SETTING_NAME = Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED
- const val DISABLED = 0
- const val ENABLED = 1
- }
+ private val userMap = mutableMapOf<Int, Flow<Boolean>>()
override fun isEnabled(userHandle: UserHandle): Flow<Boolean> =
- secureSettings
- .observerFlow(userHandle.identifier, SETTING_NAME)
- .onStart { emit(Unit) }
- .map { secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED }
- .distinctUntilChanged()
- .flowOn(bgCoroutineContext)
+ userMap.getOrPut(userHandle.identifier) {
+ secureSettings
+ .observerFlow(userHandle.identifier, SETTING_NAME)
+ .onStart { emit(Unit) }
+ .map {
+ secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED
+ }
+ .distinctUntilChanged()
+ .flowOn(bgCoroutineContext)
+ .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1)
+ }
override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean =
withContext(bgCoroutineContext) {
@@ -70,4 +76,10 @@ constructor(
userHandle.identifier
)
}
+
+ companion object {
+ private const val SETTING_NAME = Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED
+ private const val DISABLED = 0
+ private const val ENABLED = 1
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
index bbf10c509e50..419eada91f87 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorInversionRepository.kt
@@ -19,16 +19,20 @@ package com.android.systemui.accessibility.data.repository
import android.os.UserHandle
import android.provider.Settings.Secure
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.withContext
/** Provides data related to color inversion. */
@@ -45,16 +49,24 @@ class ColorInversionRepositoryImpl
@Inject
constructor(
@Background private val bgCoroutineContext: CoroutineContext,
+ @Application private val scope: CoroutineScope,
private val secureSettings: SecureSettings,
) : ColorInversionRepository {
+ private val userMap = mutableMapOf<Int, Flow<Boolean>>()
+
override fun isEnabled(userHandle: UserHandle): Flow<Boolean> =
- secureSettings
- .observerFlow(userHandle.identifier, SETTING_NAME)
- .onStart { emit(Unit) }
- .map { secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED }
- .distinctUntilChanged()
- .flowOn(bgCoroutineContext)
+ userMap.getOrPut(userHandle.identifier) {
+ secureSettings
+ .observerFlow(userHandle.identifier, SETTING_NAME)
+ .onStart { emit(Unit) }
+ .map {
+ secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED
+ }
+ .distinctUntilChanged()
+ .flowOn(bgCoroutineContext)
+ .shareIn(scope, SharingStarted.WhileSubscribed(), replay = 1)
+ }
override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean =
withContext(bgCoroutineContext) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index ab23564a1df4..57e308ff16e8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -399,7 +399,8 @@ public class AuthContainerView extends LinearLayout
config.mPromptInfo,
config.mUserId,
config.mOperationId,
- new BiometricModalities(fpProps, faceProps));
+ new BiometricModalities(fpProps, faceProps),
+ config.mOpPackageName);
final BiometricPromptLayout view = (BiometricPromptLayout) layoutInflater.inflate(
R.layout.biometric_prompt_layout, null, false);
@@ -470,7 +471,8 @@ public class AuthContainerView extends LinearLayout
mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
mPromptSelectorInteractorProvider.get().useCredentialsForAuthentication(
- mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId);
+ mConfig.mPromptInfo, credentialType, mConfig.mUserId, mConfig.mOperationId,
+ mConfig.mOpPackageName);
final CredentialViewModel vm = mCredentialViewModelProvider.get();
vm.setAnimateContents(animateContents);
((CredentialView) mCredentialView).init(vm, this, mPanelController, animatePanel);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
index 86802a5b58b0..02eae9cedf74 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java
@@ -148,6 +148,12 @@ public class UdfpsDialogMeasureAdapter {
|| child.getId() == R.id.customized_view_container) {
//skip description view and compute later
continue;
+ } else if (child.getId() == R.id.logo) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(child.getLayoutParams().width,
+ MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
+ MeasureSpec.EXACTLY));
} else {
child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
index b35fbbc7bb32..ad7bb0e61178 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/PromptRepository.kt
@@ -55,6 +55,9 @@ interface PromptRepository {
/** The kind of credential to use (biometric, pin, pattern, etc.). */
val kind: StateFlow<PromptKind>
+ /** The package name that the prompt is called from. */
+ val opPackageName: StateFlow<String?>
+
/**
* If explicit confirmation is required.
*
@@ -68,6 +71,7 @@ interface PromptRepository {
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
+ opPackageName: String,
)
/** Unset the prompt info. */
@@ -108,6 +112,9 @@ constructor(
private val _kind: MutableStateFlow<PromptKind> = MutableStateFlow(PromptKind.Biometric())
override val kind = _kind.asStateFlow()
+ private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null)
+ override val opPackageName = _opPackageName.asStateFlow()
+
private val _faceSettings =
_userId.map { id -> faceSettings.forUser(id) }.distinctUntilChanged()
private val _faceSettingAlwaysRequireConfirmation =
@@ -127,11 +134,13 @@ constructor(
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
+ opPackageName: String,
) {
_kind.value = kind
_userId.value = userId
_challenge.value = gatekeeperChallenge
_promptInfo.value = promptInfo
+ _opPackageName.value = opPackageName
}
override fun unsetPrompt() {
@@ -139,6 +148,7 @@ constructor(
_userId.value = null
_challenge.value = null
_kind.value = PromptKind.Biometric()
+ _opPackageName.value = null
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
index ac4b717a23ec..359e2e703ee6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractor.kt
@@ -115,12 +115,14 @@ constructor(
@Utils.CredentialType kind: Int,
userId: Int,
challenge: Long,
+ opPackageName: String,
) {
biometricPromptRepository.setPrompt(
promptInfo,
userId,
challenge,
- kind.asBiometricPromptCredential()
+ kind.asBiometricPromptCredential(),
+ opPackageName,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index 65a2c0a2490f..b3f95748ccdc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -76,6 +76,7 @@ interface PromptSelectorInteractor {
userId: Int,
challenge: Long,
modalities: BiometricModalities,
+ opPackageName: String,
)
/** Use credential-based authentication instead of biometrics. */
@@ -84,6 +85,7 @@ interface PromptSelectorInteractor {
@Utils.CredentialType kind: Int,
userId: Int,
challenge: Long,
+ opPackageName: String,
)
/** Unset the current authentication request. */
@@ -104,9 +106,12 @@ constructor(
promptRepository.promptInfo,
promptRepository.challenge,
promptRepository.userId,
- promptRepository.kind
- ) { promptInfo, challenge, userId, kind ->
- if (promptInfo == null || userId == null || challenge == null) {
+ promptRepository.kind,
+ promptRepository.opPackageName,
+ ) { promptInfo, challenge, userId, kind, opPackageName ->
+ if (
+ promptInfo == null || userId == null || challenge == null || opPackageName == null
+ ) {
return@combine null
}
@@ -117,6 +122,7 @@ constructor(
userInfo = BiometricUserInfo(userId = userId),
operationInfo = BiometricOperationInfo(gatekeeperChallenge = challenge),
modalities = kind.activeModalities,
+ opPackageName = opPackageName,
)
else -> null
}
@@ -152,13 +158,15 @@ constructor(
promptInfo: PromptInfo,
userId: Int,
challenge: Long,
- modalities: BiometricModalities
+ modalities: BiometricModalities,
+ opPackageName: String,
) {
promptRepository.setPrompt(
promptInfo = promptInfo,
userId = userId,
gatekeeperChallenge = challenge,
kind = PromptKind.Biometric(modalities),
+ opPackageName = opPackageName,
)
}
@@ -167,12 +175,14 @@ constructor(
@Utils.CredentialType kind: Int,
userId: Int,
challenge: Long,
+ opPackageName: String,
) {
promptRepository.setPrompt(
promptInfo = promptInfo,
userId = userId,
gatekeeperChallenge = challenge,
kind = kind.asBiometricPromptCredential(),
+ opPackageName = opPackageName,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
index 437793798567..c17c8dced668 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.biometrics.domain.model
+import android.graphics.Bitmap
import android.hardware.biometrics.PromptContentView
import android.hardware.biometrics.PromptInfo
import com.android.systemui.biometrics.shared.model.BiometricModalities
@@ -26,6 +27,7 @@ sealed class BiometricPromptRequest(
userInfo: BiometricUserInfo,
operationInfo: BiometricOperationInfo,
val modalities: BiometricModalities,
+ val opPackageName: String,
) :
BiometricPromptRequest(
title = info.title?.toString() ?: "",
@@ -36,6 +38,8 @@ sealed class BiometricPromptRequest(
showEmergencyCallButton = info.isShowEmergencyCallButton
) {
val contentView: PromptContentView? = info.contentView
+ val logoRes: Int = info.logoRes
+ val logoBitmap: Bitmap? = info.logoBitmap
val negativeButtonText: String = info.negativeButtonText?.toString() ?: ""
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
index 60b454e9670e..b450896729b7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricPromptLayout.java
@@ -115,6 +115,12 @@ public class BiometricPromptLayout extends LinearLayout {
MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().height,
MeasureSpec.EXACTLY));
+ } else if (child.getId() == R.id.logo) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(child.getLayoutParams().width,
+ MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
+ MeasureSpec.EXACTLY));
} else if (child.getId() == R.id.biometric_icon) {
child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
index 22b02da5a7d5..16e7f05fae37 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt
@@ -20,9 +20,9 @@ import android.content.Context
import android.content.res.Resources
import android.content.res.Resources.Theme
import android.graphics.Paint
-import android.hardware.biometrics.PromptContentListItem
-import android.hardware.biometrics.PromptContentListItemBulletedText
-import android.hardware.biometrics.PromptContentListItemPlainText
+import android.hardware.biometrics.PromptContentItem
+import android.hardware.biometrics.PromptContentItemBulletedText
+import android.hardware.biometrics.PromptContentItemPlainText
import android.hardware.biometrics.PromptContentView
import android.hardware.biometrics.PromptVerticalListContentView
import android.text.SpannableString
@@ -111,21 +111,21 @@ private fun createNewRowLayout(inflater: LayoutInflater): LinearLayout {
return inflater.inflate(R.layout.biometric_prompt_content_row_layout, null) as LinearLayout
}
-private fun PromptContentListItem.doesExceedMaxLinesIfTwoColumn(
+private fun PromptContentItem.doesExceedMaxLinesIfTwoColumn(
resources: Resources,
): Boolean {
val passedInText: CharSequence =
when (this) {
- is PromptContentListItemPlainText -> text
- is PromptContentListItemBulletedText -> text
+ is PromptContentItemPlainText -> text
+ is PromptContentItemBulletedText -> text
else -> {
- throw IllegalStateException("No such ListItem: $this")
+ throw IllegalStateException("No such PromptContentItem: $this")
}
}
when (this) {
- is PromptContentListItemPlainText,
- is PromptContentListItemBulletedText -> {
+ is PromptContentItemPlainText,
+ is PromptContentItemBulletedText -> {
val dialogMargin =
resources.getDimensionPixelSize(R.dimen.biometric_dialog_border_padding)
val halfDialogWidth =
@@ -155,12 +155,12 @@ private fun PromptContentListItem.doesExceedMaxLinesIfTwoColumn(
return numLines > maxLines
}
else -> {
- throw IllegalStateException("No such ListItem: $this")
+ throw IllegalStateException("No such PromptContentItem: $this")
}
}
}
-private fun PromptContentListItem.toView(
+private fun PromptContentItem.toView(
resources: Resources,
inflater: LayoutInflater,
theme: Theme,
@@ -171,10 +171,10 @@ private fun PromptContentListItem.toView(
textView.layoutParams = lp
when (this) {
- is PromptContentListItemPlainText -> {
+ is PromptContentItemPlainText -> {
textView.text = text
}
- is PromptContentListItemBulletedText -> {
+ is PromptContentItemBulletedText -> {
val bulletedText = SpannableString(text)
val span =
BulletSpan(
@@ -186,25 +186,25 @@ private fun PromptContentListItem.toView(
textView.text = bulletedText
}
else -> {
- throw IllegalStateException("No such ListItem: $this")
+ throw IllegalStateException("No such PromptContentItem: $this")
}
}
return textView
}
-private fun PromptContentListItem.getListItemPadding(resources: Resources): Int {
+private fun PromptContentItem.getListItemPadding(resources: Resources): Int {
var listItemPadding =
resources.getDimensionPixelSize(
R.dimen.biometric_prompt_content_list_item_padding_horizontal
) * 2
when (this) {
- is PromptContentListItemPlainText -> {}
- is PromptContentListItemBulletedText -> {
+ is PromptContentItemPlainText -> {}
+ is PromptContentItemBulletedText -> {
listItemPadding +=
getListItemBulletRadius(resources) * 2 + getListItemBulletGapWidth(resources)
}
else -> {
- throw IllegalStateException("No such ListItem: $this")
+ throw IllegalStateException("No such PromptContentItem: $this")
}
}
return listItemPadding
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 04dc7a8da9f3..285ab4a800b6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -31,6 +31,7 @@ import android.view.View
import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
import android.view.accessibility.AccessibilityManager
import android.widget.Button
+import android.widget.ImageView
import android.widget.ScrollView
import android.widget.TextView
import androidx.lifecycle.DefaultLifecycleObserver
@@ -92,6 +93,7 @@ object BiometricViewBinder {
val textColorHint =
view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme)
+ val logoView = view.requireViewById<ImageView>(R.id.logo)
val titleView = view.requireViewById<TextView>(R.id.title)
val subtitleView = view.requireViewById<TextView>(R.id.subtitle)
val descriptionView = view.requireViewById<TextView>(R.id.description)
@@ -99,6 +101,8 @@ object BiometricViewBinder {
view.requireViewById<ScrollView>(R.id.customized_view_container)
// set selected to enable marquee unless a screen reader is enabled
+ logoView.isSelected =
+ !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
titleView.isSelected =
!accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
subtitleView.isSelected =
@@ -152,6 +156,7 @@ object BiometricViewBinder {
}
}
+ logoView.setImageDrawable(viewModel.logo.first())
titleView.text = viewModel.title.first()
subtitleView.text = viewModel.subtitle.first()
descriptionView.text = viewModel.description.first()
@@ -183,6 +188,7 @@ object BiometricViewBinder {
viewModel = viewModel,
viewsToHideWhenSmall =
listOf(
+ logoView,
titleView,
subtitleView,
descriptionView,
@@ -190,6 +196,7 @@ object BiometricViewBinder {
),
viewsToFadeInOnSizeChange =
listOf(
+ logoView,
titleView,
subtitleView,
descriptionView,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index c3bbaedb2670..d5695f31f121 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -25,6 +25,7 @@ import android.view.ViewGroup
import android.view.WindowInsets
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
+import android.widget.ImageView
import android.widget.TextView
import androidx.core.animation.addListener
import androidx.core.view.doOnLayout
@@ -234,7 +235,13 @@ private fun View.isLandscape(): Boolean {
private fun View.showContentOrHide(forceHide: Boolean = false) {
val isTextViewWithBlankText = this is TextView && this.text.isBlank()
- visibility = if (forceHide || isTextViewWithBlankText) View.GONE else View.VISIBLE
+ val isImageViewWithoutImage = this is ImageView && this.drawable == null
+ visibility =
+ if (forceHide || isTextViewWithBlankText || isImageViewWithoutImage) {
+ View.GONE
+ } else {
+ View.VISIBLE
+ }
}
private fun View.asVerticalAnimator(
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 1c789283ec70..dca0338dc8e7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -18,6 +18,8 @@ package com.android.systemui.biometrics.ui.viewmodel
import android.content.Context
import android.graphics.Rect
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Drawable
import android.hardware.biometrics.BiometricPrompt
import android.hardware.biometrics.PromptContentView
import android.util.Log
@@ -233,6 +235,19 @@ constructor(
}
}
+ /** Logo for the prompt. */
+ val logo: Flow<Drawable?> =
+ promptSelectorInteractor.prompt
+ .map {
+ when {
+ it == null -> null
+ it.logoRes != -1 -> context.resources.getDrawable(it.logoRes, context.theme)
+ it.logoBitmap != null -> BitmapDrawable(context.resources, it.logoBitmap)
+ else -> context.packageManager.getApplicationIcon(it.opPackageName)
+ }
+ }
+ .distinctUntilChanged()
+
/** Title for the prompt. */
val title: Flow<String> =
promptSelectorInteractor.prompt.map { it?.title ?: "" }.distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 10768ea6122a..dc07c1b25678 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -18,6 +18,7 @@ package com.android.systemui.communal.dagger
import com.android.systemui.communal.data.db.CommunalDatabaseModule
import com.android.systemui.communal.data.repository.CommunalMediaRepositoryModule
+import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryModule
import com.android.systemui.communal.data.repository.CommunalRepositoryModule
import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule
import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
@@ -34,6 +35,7 @@ import dagger.Module
CommunalTutorialRepositoryModule::class,
CommunalWidgetRepositoryModule::class,
CommunalDatabaseModule::class,
+ CommunalPrefsRepositoryModule::class,
]
)
interface CommunalModule {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
new file mode 100644
index 000000000000..c2ea2e93ce58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.data.repository
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.content.pm.UserInfo
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserFileManagerExt.observeSharedPreferences
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+/**
+ * Stores simple preferences for the current user in communal hub. For use cases like "has the CTA
+ * tile been dismissed?"
+ */
+interface CommunalPrefsRepository {
+
+ /** Whether the CTA tile has been dismissed. */
+ val isCtaDismissed: Flow<Boolean>
+
+ /** Save the CTA tile dismissed state for the current user. */
+ suspend fun setCtaDismissedForCurrentUser()
+}
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class CommunalPrefsRepositoryImpl
+@Inject
+constructor(
+ @Background private val backgroundScope: CoroutineScope,
+ @Background private val bgDispatcher: CoroutineDispatcher,
+ private val userRepository: UserRepository,
+ private val userFileManager: UserFileManager,
+) : CommunalPrefsRepository {
+
+ override val isCtaDismissed: Flow<Boolean> =
+ userRepository.selectedUserInfo
+ .flatMapLatest(::observeCtaDismissState)
+ .stateIn(
+ scope = backgroundScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
+ override suspend fun setCtaDismissedForCurrentUser() =
+ withContext(bgDispatcher) {
+ getSharedPrefsForUser(userRepository.getSelectedUserInfo())
+ .edit()
+ .putBoolean(CTA_DISMISSED_STATE, true)
+ .apply()
+ }
+
+ private fun observeCtaDismissState(user: UserInfo): Flow<Boolean> =
+ userFileManager
+ .observeSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, user.id)
+ // Emit at the start of collection to ensure we get an initial value
+ .onStart { emit(Unit) }
+ .map { getCtaDismissedState() }
+ .flowOn(bgDispatcher)
+
+ private suspend fun getCtaDismissedState(): Boolean =
+ withContext(bgDispatcher) {
+ getSharedPrefsForUser(userRepository.getSelectedUserInfo())
+ .getBoolean(CTA_DISMISSED_STATE, false)
+ }
+
+ private fun getSharedPrefsForUser(user: UserInfo): SharedPreferences {
+ return userFileManager.getSharedPreferences(
+ FILE_NAME,
+ Context.MODE_PRIVATE,
+ user.id,
+ )
+ }
+
+ companion object {
+ const val TAG = "CommunalRepository"
+ const val FILE_NAME = "communal_hub_prefs"
+ const val CTA_DISMISSED_STATE = "cta_dismissed"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt
new file mode 100644
index 000000000000..a4ff6d3dfbef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryModule.kt
@@ -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.
+ *
+ */
+
+package com.android.systemui.communal.data.repository
+
+import dagger.Binds
+import dagger.Module
+
+@Module
+interface CommunalPrefsRepositoryModule {
+ @Binds fun communalPrefsRepository(impl: CommunalPrefsRepositoryImpl): CommunalPrefsRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
index 553b3ebc0813..1f4be4060223 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalRepository.kt
@@ -56,9 +56,6 @@ interface CommunalRepository {
/** Exposes the transition state of the communal [SceneTransitionLayout]. */
val transitionState: StateFlow<ObservableCommunalTransitionState>
- /** Whether the CTA tile is visible in the hub under view mode. */
- val isCtaTileInViewModeVisible: Flow<Boolean>
-
/** Updates the requested scene. */
fun setDesiredScene(desiredScene: CommunalSceneKey)
@@ -68,9 +65,6 @@ interface CommunalRepository {
* Note that you must call is with `null` when the UI is done or risk a memory leak.
*/
fun setTransitionState(transitionState: Flow<ObservableCommunalTransitionState>?)
-
- /** Updates whether to display the CTA tile in the hub under view mode. */
- fun setCtaTileInViewModeVisibility(isVisible: Boolean)
}
@OptIn(ExperimentalCoroutinesApi::class)
@@ -102,16 +96,6 @@ constructor(
initialValue = defaultTransitionState,
)
- // TODO(b/313462210) - persist the value in local storage, so the tile won't show up again
- // once dismissed.
- private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true)
- override val isCtaTileInViewModeVisible: Flow<Boolean> =
- _isCtaTileInViewModeVisible.asStateFlow()
-
- override fun setCtaTileInViewModeVisibility(isVisible: Boolean) {
- _isCtaTileInViewModeVisible.value = isVisible
- }
-
override fun setDesiredScene(desiredScene: CommunalSceneKey) {
_desiredScene.value = desiredScene
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
index 9a9b0e29cbc4..046aaaa33342 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.data.repository
import android.provider.Settings
+import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED
import android.provider.Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED
import android.provider.Settings.Secure.HubModeTutorialState
import com.android.systemui.dagger.SysUISingleton
@@ -24,7 +25,6 @@ import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
-import com.android.systemui.settings.UserTracker
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
@@ -35,6 +35,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
@@ -62,14 +63,19 @@ class CommunalTutorialRepositoryImpl
constructor(
@Application private val applicationScope: CoroutineScope,
@Background private val backgroundDispatcher: CoroutineDispatcher,
- userRepository: UserRepository,
+ private val userRepository: UserRepository,
private val secureSettings: SecureSettings,
- private val userTracker: UserTracker,
@CommunalLog logBuffer: LogBuffer,
) : CommunalTutorialRepository {
companion object {
private const val TAG = "CommunalTutorialRepository"
+
+ const val MIN_TUTORIAL_VERSION = HUB_MODE_TUTORIAL_COMPLETED
+
+ // A version number which ensures that users, regardless of their completion of previous
+ // versions, see the updated tutorial when this number is bumped.
+ const val CURRENT_TUTORIAL_VERSION = MIN_TUTORIAL_VERSION + 1
}
private data class SettingsState(
@@ -80,7 +86,7 @@ constructor(
private val settingsState: Flow<SettingsState> =
userRepository.selectedUserInfo
- .flatMapLatest { observeSettings() }
+ .flatMapLatest { userInfo -> observeSettings(userInfo.id) }
.shareIn(scope = applicationScope, started = SharingStarted.WhileSubscribed())
/** Emits the state of tutorial state in settings */
@@ -91,31 +97,37 @@ constructor(
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = HUB_MODE_TUTORIAL_NOT_STARTED
+ initialValue = HUB_MODE_TUTORIAL_NOT_STARTED,
)
- private fun observeSettings(): Flow<SettingsState> =
+ private fun observeSettings(userId: Int): Flow<SettingsState> =
secureSettings
.observerFlow(
- userId = userTracker.userId,
- names =
- arrayOf(
- Settings.Secure.HUB_MODE_TUTORIAL_STATE,
- )
+ userId = userId,
+ names = arrayOf(Settings.Secure.HUB_MODE_TUTORIAL_STATE),
)
// Force an update
.onStart { emit(Unit) }
- .map { readFromSettings() }
+ .map { readFromSettings(userId) }
- private suspend fun readFromSettings(): SettingsState =
+ private suspend fun readFromSettings(userId: Int): SettingsState =
withContext(backgroundDispatcher) {
- val userId = userTracker.userId
- val hubModeTutorialState =
+ var hubModeTutorialState =
secureSettings.getIntForUser(
Settings.Secure.HUB_MODE_TUTORIAL_STATE,
HUB_MODE_TUTORIAL_NOT_STARTED,
userId,
)
+
+ if (hubModeTutorialState >= CURRENT_TUTORIAL_VERSION) {
+ // Tutorial is considered "completed" if the user has completed the current or a
+ // newer version.
+ hubModeTutorialState = HUB_MODE_TUTORIAL_COMPLETED
+ } else if (hubModeTutorialState >= MIN_TUTORIAL_VERSION) {
+ // Tutorial is considered "not started" if the user completed a version older than
+ // the current.
+ hubModeTutorialState = HUB_MODE_TUTORIAL_NOT_STARTED
+ }
val settingsState = SettingsState(hubModeTutorialState)
logger.d({ "Communal tutorial state for user $int1 in settings: $str1" }) {
int1 = userId
@@ -127,18 +139,40 @@ constructor(
override suspend fun setTutorialState(state: Int): Unit =
withContext(backgroundDispatcher) {
- val userId = userTracker.userId
+ val userId = userRepository.getSelectedUserInfo().id
if (tutorialSettingState.value == state) {
return@withContext
}
+ val newState =
+ if (state == HUB_MODE_TUTORIAL_COMPLETED) CURRENT_TUTORIAL_VERSION else state
logger.d({ "Update communal tutorial state to $int1 for user $int2" }) {
- int1 = state
+ int1 = newState
int2 = userId
}
secureSettings.putIntForUser(
Settings.Secure.HUB_MODE_TUTORIAL_STATE,
- state,
+ newState,
userId,
)
}
}
+
+// TODO(b/320769333): delete me and use the real repo above when tutorial is ready.
+@SysUISingleton
+class CommunalTutorialDisabledRepositoryImpl
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+) : CommunalTutorialRepository {
+ override val tutorialSettingState: StateFlow<Int> =
+ emptyFlow<Int>()
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = HUB_MODE_TUTORIAL_COMPLETED,
+ )
+
+ override suspend fun setTutorialState(state: Int) {
+ // Do nothing
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt
index 69b0a27c55a8..b4f838ad5567 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryModule.kt
@@ -22,6 +22,9 @@ import dagger.Module
@Module
interface CommunalTutorialRepositoryModule {
+ // TODO(b/320769333): use [CommunalTutorialRepositoryImpl] when tutorial is ready.
@Binds
- fun communalTutorialRepository(impl: CommunalTutorialRepositoryImpl): CommunalTutorialRepository
+ fun communalTutorialRepository(
+ impl: CommunalTutorialDisabledRepositoryImpl
+ ): CommunalTutorialRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
index e6816e954b5d..1c362e993509 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepository.kt
@@ -16,26 +16,21 @@
package com.android.systemui.communal.data.repository
-import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.content.ComponentName
-import android.content.Intent
-import android.content.IntentFilter
-import android.os.UserManager
import androidx.annotation.WorkerThread
-import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.data.db.CommunalItemRank
import com.android.systemui.communal.data.db.CommunalWidgetDao
import com.android.systemui.communal.data.db.CommunalWidgetItem
import com.android.systemui.communal.shared.CommunalWidgetHost
import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
-import com.android.systemui.settings.UserTracker
import java.util.Optional
import javax.inject.Inject
import kotlin.coroutines.cancellation.CancellationException
@@ -43,16 +38,12 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
/** Encapsulates the state of widgets for communal mode. */
interface CommunalWidgetRepository {
@@ -75,23 +66,21 @@ interface CommunalWidgetRepository {
* @param widgetIdToPriorityMap mapping of the widget ids to the priority of the widget.
*/
fun updateWidgetOrder(widgetIdToPriorityMap: Map<Int, Int>) {}
+
+ /** Update whether the app widget host should be active. */
+ fun updateAppWidgetHostActive(active: Boolean)
}
-@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CommunalWidgetRepositoryImpl
@Inject
constructor(
private val appWidgetManager: Optional<AppWidgetManager>,
- private val appWidgetHost: AppWidgetHost,
+ private val appWidgetHost: CommunalAppWidgetHost,
@Application private val applicationScope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher,
- broadcastDispatcher: BroadcastDispatcher,
- communalRepository: CommunalRepository,
private val communalWidgetHost: CommunalWidgetHost,
private val communalWidgetDao: CommunalWidgetDao,
- private val userManager: UserManager,
- private val userTracker: UserTracker,
@CommunalLog logBuffer: LogBuffer,
) : CommunalWidgetRepository {
companion object {
@@ -100,40 +89,22 @@ constructor(
private val logger = Logger(logBuffer, TAG)
- // Whether the [AppWidgetHost] is listening for updates.
- private var isHostListening = false
-
- private suspend fun isUserUnlockingOrUnlocked(): Boolean =
- withContext(bgDispatcher) { userManager.isUserUnlockingOrUnlocked(userTracker.userHandle) }
-
- private val isUserUnlocked: Flow<Boolean> =
- flowOf(communalRepository.isCommunalEnabled)
- .flatMapLatest { enabled ->
- if (enabled) {
- broadcastDispatcher
- .broadcastFlow(
- filter = IntentFilter(Intent.ACTION_USER_UNLOCKED),
- user = userTracker.userHandle
- )
- .onStart { emit(Unit) }
- .mapLatest { isUserUnlockingOrUnlocked() }
- } else {
- emptyFlow()
- }
- }
- .distinctUntilChanged()
-
- private val isHostActive: Flow<Boolean> =
- isUserUnlocked.map {
- if (it) {
- startListening()
- true
- } else {
- stopListening()
- false
- }
+ override fun updateAppWidgetHostActive(active: Boolean) {
+ if (active == isHostActive.value) {
+ return
+ }
+
+ if (active) {
+ appWidgetHost.startListening()
+ } else {
+ appWidgetHost.stopListening()
}
+ isHostActive.value = active
+ }
+
+ private val isHostActive = MutableStateFlow(false)
+ @OptIn(ExperimentalCoroutinesApi::class)
override val communalWidgets: Flow<List<CommunalWidgetContentModel>> =
isHostActive.flatMapLatest { isHostActive ->
if (!isHostActive || !appWidgetManager.isPresent) {
@@ -218,22 +189,4 @@ constructor(
priority = entry.key.rank,
)
}
-
- private fun startListening() {
- if (isHostListening) {
- return
- }
-
- appWidgetHost.startListening()
- isHostListening = true
- }
-
- private fun stopListening() {
- if (!isHostListening) {
- return
- }
-
- appWidgetHost.stopListening()
- isHostListening = false
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
index d0d9e3fabc7b..52f42c1aba4f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalWidgetRepositoryModule.kt
@@ -17,11 +17,11 @@
package com.android.systemui.communal.data.repository
-import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.res.Resources
import com.android.systemui.communal.shared.CommunalWidgetHost
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
@@ -48,15 +48,15 @@ interface CommunalWidgetRepositoryModule {
@SysUISingleton
@Provides
- fun provideAppWidgetHost(@Application context: Context): AppWidgetHost {
- return AppWidgetHost(context, APP_WIDGET_HOST_ID)
+ fun provideCommunalAppWidgetHost(@Application context: Context): CommunalAppWidgetHost {
+ return CommunalAppWidgetHost(context, APP_WIDGET_HOST_ID)
}
@SysUISingleton
@Provides
fun provideCommunalWidgetHost(
appWidgetManager: Optional<AppWidgetManager>,
- appWidgetHost: AppWidgetHost,
+ appWidgetHost: CommunalAppWidgetHost,
@CommunalLog logBuffer: LogBuffer,
): CommunalWidgetHost {
return CommunalWidgetHost(appWidgetManager, appWidgetHost, logBuffer)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 9fa4cd6c7985..71523b9e750f 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -17,9 +17,9 @@
package com.android.systemui.communal.domain.interactor
import android.app.smartspace.SmartspaceTarget
-import android.appwidget.AppWidgetHost
import android.content.ComponentName
import com.android.systemui.communal.data.repository.CommunalMediaRepository
+import com.android.systemui.communal.data.repository.CommunalPrefsRepository
import com.android.systemui.communal.data.repository.CommunalRepository
import com.android.systemui.communal.data.repository.CommunalWidgetRepository
import com.android.systemui.communal.domain.model.CommunalContentModel
@@ -29,30 +29,40 @@ import com.android.systemui.communal.shared.model.CommunalContentSize.HALF
import com.android.systemui.communal.shared.model.CommunalContentSize.THIRD
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.smartspace.data.repository.SmartspaceRepository
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
/** Encapsulates business-logic related to communal mode. */
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class CommunalInteractor
@Inject
constructor(
+ @Application private val applicationScope: CoroutineScope,
private val communalRepository: CommunalRepository,
private val widgetRepository: CommunalWidgetRepository,
+ private val communalPrefsRepository: CommunalPrefsRepository,
mediaRepository: CommunalMediaRepository,
smartspaceRepository: SmartspaceRepository,
keyguardInteractor: KeyguardInteractor,
- private val appWidgetHost: AppWidgetHost,
+ private val appWidgetHost: CommunalAppWidgetHost,
private val editWidgetsActivityStarter: EditWidgetsActivityStarter
) {
@@ -60,6 +70,20 @@ constructor(
val isCommunalEnabled: Boolean
get() = communalRepository.isCommunalEnabled
+ /** Whether communal features are enabled and available. */
+ val isCommunalAvailable: StateFlow<Boolean> =
+ flowOf(isCommunalEnabled)
+ .flatMapLatest { enabled ->
+ if (enabled) keyguardInteractor.isEncryptedOrLockdown.map { !it } else flowOf(false)
+ }
+ .distinctUntilChanged()
+ .onEach { available -> widgetRepository.updateAppWidgetHostActive(available) }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false,
+ )
+
/**
* Target scene as requested by the underlying [SceneTransitionLayout] or through
* [onSceneChanged].
@@ -122,7 +146,7 @@ constructor(
}
/** Dismiss the CTA tile from the hub in view mode. */
- fun dismissCtaTile() = communalRepository.setCtaTileInViewModeVisibility(isVisible = false)
+ suspend fun dismissCtaTile() = communalPrefsRepository.setCtaDismissedForCurrentUser()
/**
* Add a widget at the specified position.
@@ -174,8 +198,8 @@ constructor(
/** CTA tile to be displayed in the glanceable hub (view mode). */
val ctaTileContent: Flow<List<CommunalContentModel.CtaTileInViewMode>> =
- communalRepository.isCtaTileInViewModeVisible.map { visible ->
- if (visible) listOf(CommunalContentModel.CtaTileInViewMode()) else emptyList()
+ communalPrefsRepository.isCtaDismissed.map { isDismissed ->
+ if (isDismissed) emptyList() else listOf(CommunalContentModel.CtaTileInViewMode())
}
/** A list of tutorial content to be displayed in the communal hub in tutorial mode. */
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
index 46f957f3aaf2..0d52afd4fff5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/model/CommunalContentModel.kt
@@ -16,10 +16,10 @@
package com.android.systemui.communal.domain.model
-import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetProviderInfo
import android.widget.RemoteViews
import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import java.util.UUID
/** Encapsulates data for a communal content. */
@@ -44,7 +44,7 @@ sealed interface CommunalContentModel {
class Widget(
val appWidgetId: Int,
val providerInfo: AppWidgetProviderInfo,
- val appWidgetHost: AppWidgetHost,
+ val appWidgetHost: CommunalAppWidgetHost,
) : CommunalContentModel {
override val key = KEY.widget(appWidgetId)
// Widget size is always half.
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
index 41f9cb4c98ed..7fe37ccf0dc8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/CommunalWidgetHost.kt
@@ -16,11 +16,11 @@
package com.android.systemui.communal.shared
-import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_CONFIGURATION_OPTIONAL
import android.appwidget.AppWidgetProviderInfo.WIDGET_FEATURE_RECONFIGURABLE
import android.content.ComponentName
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.core.Logger
import com.android.systemui.log.dagger.CommunalLog
@@ -35,7 +35,7 @@ class CommunalWidgetHost
@Inject
constructor(
private val appWidgetManager: Optional<AppWidgetManager>,
- private val appWidgetHost: AppWidgetHost,
+ private val appWidgetHost: CommunalAppWidgetHost,
@CommunalLog logBuffer: LogBuffer,
) {
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 84708a49f469..73bb0b0ec8a3 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -24,6 +24,7 @@ import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
import com.android.systemui.media.controls.ui.MediaHost
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flowOf
@@ -32,10 +33,21 @@ abstract class BaseCommunalViewModel(
private val communalInteractor: CommunalInteractor,
val mediaHost: MediaHost,
) {
+ val isCommunalAvailable: StateFlow<Boolean> = communalInteractor.isCommunalAvailable
+
val isKeyguardVisible: Flow<Boolean> = communalInteractor.isKeyguardVisible
val currentScene: StateFlow<CommunalSceneKey> = communalInteractor.desiredScene
+ /** Whether widgets are currently being re-ordered. */
+ open val reorderingWidgets: StateFlow<Boolean> = MutableStateFlow(false)
+
+ private val _selectedIndex: MutableStateFlow<Int?> = MutableStateFlow(null)
+
+ /** The index of the currently selected item, or null if no item selected. */
+ val selectedIndex: StateFlow<Int?>
+ get() = _selectedIndex
+
fun onSceneChanged(scene: CommunalSceneKey) {
communalInteractor.onSceneChanged(scene)
}
@@ -105,4 +117,9 @@ abstract class BaseCommunalViewModel(
/** Called as the user cancels dragging a widget to reorder. */
open fun onReorderWidgetCancel() {}
+
+ /** Set the index of the currently selected item */
+ fun setSelectedIndex(index: Int?) {
+ _selectedIndex.value = index
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 7faf653cc177..317dd4040e7c 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -20,7 +20,6 @@ import android.app.Activity
import android.app.Activity.RESULT_CANCELED
import android.app.Activity.RESULT_OK
import android.app.ActivityOptions
-import android.appwidget.AppWidgetHost
import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.widget.RemoteViews
@@ -28,6 +27,7 @@ import com.android.internal.logging.UiEventLogger
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.model.CommunalContentModel
import com.android.systemui.communal.shared.log.CommunalUiEvent
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.media.controls.ui.MediaHost
import com.android.systemui.media.dagger.MediaModule
@@ -37,7 +37,10 @@ import javax.inject.Named
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
/** The view model for communal hub in edit mode. */
@SysUISingleton
@@ -45,7 +48,7 @@ class CommunalEditModeViewModel
@Inject
constructor(
private val communalInteractor: CommunalInteractor,
- private val appWidgetHost: AppWidgetHost,
+ private val appWidgetHost: CommunalAppWidgetHost,
@Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost,
private val uiEventLogger: UiEventLogger,
) : BaseCommunalViewModel(communalInteractor, mediaHost) {
@@ -69,9 +72,15 @@ constructor(
// Only widgets are editable. The CTA tile comes last in the list and remains visible.
override val communalContent: Flow<List<CommunalContentModel>> =
- communalInteractor.widgetContent.map { widgets ->
- widgets + listOf(CommunalContentModel.CtaTileInEditMode())
- }
+ communalInteractor.widgetContent
+ // Clear the selected index when the list is updated.
+ .onEach { setSelectedIndex(null) }
+ .map { widgets -> widgets + listOf(CommunalContentModel.CtaTileInEditMode()) }
+
+ private val _reorderingWidgets = MutableStateFlow(false)
+
+ override val reorderingWidgets: StateFlow<Boolean>
+ get() = _reorderingWidgets
override fun onDeleteWidget(id: Int) = communalInteractor.deleteWidget(id)
@@ -135,14 +144,19 @@ constructor(
}
override fun onReorderWidgetStart() {
+ // Clear selection status
+ setSelectedIndex(null)
+ _reorderingWidgets.value = true
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_START)
}
override fun onReorderWidgetEnd() {
+ _reorderingWidgets.value = false
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_FINISH)
}
override fun onReorderWidgetCancel() {
+ _reorderingWidgets.value = false
uiEventLogger.log(CommunalUiEvent.COMMUNAL_HUB_REORDER_WIDGET_CANCEL)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 09c18ed0c8de..d619362b0311 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -86,9 +86,11 @@ constructor(
override fun onOpenWidgetEditor() = communalInteractor.showWidgetEditor()
override fun onDismissCtaTile() {
- communalInteractor.dismissCtaTile()
- setPopupOnDismissCtaVisibility(true)
- schedulePopupHiding()
+ scope.launch {
+ communalInteractor.dismissCtaTile()
+ setPopupOnDismissCtaVisibility(true)
+ schedulePopupHiding()
+ }
}
override fun getInteractionHandler(): RemoteViews.InteractionHandler = interactionHandler
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
new file mode 100644
index 000000000000..003c9d50e789
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHost.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.widgets
+
+import android.appwidget.AppWidgetHost
+import android.appwidget.AppWidgetHostView
+import android.appwidget.AppWidgetProviderInfo
+import android.content.Context
+
+/** Communal app widget host that creates a [CommunalAppWidgetHostView]. */
+class CommunalAppWidgetHost(context: Context, hostId: Int) : AppWidgetHost(context, hostId) {
+ override fun onCreateView(
+ context: Context,
+ appWidgetId: Int,
+ appWidget: AppWidgetProviderInfo?
+ ): AppWidgetHostView {
+ return CommunalAppWidgetHostView(context)
+ }
+
+ /**
+ * Creates and returns a [CommunalAppWidgetHostView]. This method does the same thing as
+ * `createView`. The only difference is that the returned value will be casted to
+ * [CommunalAppWidgetHostView].
+ */
+ fun createViewForCommunal(
+ context: Context?,
+ appWidgetId: Int,
+ appWidget: AppWidgetProviderInfo?
+ ): CommunalAppWidgetHostView {
+ // `createView` internally calls `onCreateView` to create the view. We cannot override
+ // `createView`, but we are sure that the hostView is `CommunalAppWidgetHostView`
+ return createView(context, appWidgetId, appWidget) as CommunalAppWidgetHostView
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
new file mode 100644
index 000000000000..2b7d82395b89
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalAppWidgetHostView.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.widgets
+
+import android.appwidget.AppWidgetHostView
+import android.content.Context
+import android.graphics.Outline
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewOutlineProvider
+
+/** AppWidgetHostView that displays in communal hub with support for rounded corners. */
+class CommunalAppWidgetHostView(context: Context) : AppWidgetHostView(context) {
+ // Mutable corner radius.
+ var enforcedCornerRadius: Float
+
+ // Mutable `Rect`. The size will be mutated when the widget is reapplied.
+ var enforcedRectangle: Rect
+
+ init {
+ enforcedCornerRadius = RoundedCornerEnforcement.computeEnforcedRadius(context)
+ enforcedRectangle = Rect()
+ }
+
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+ super.onLayout(changed, l, t, r, b)
+
+ enforceRoundedCorners()
+ }
+
+ private val cornerRadiusEnforcementOutline: ViewOutlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View?, outline: Outline) {
+ if (enforcedRectangle.isEmpty || enforcedCornerRadius <= 0) {
+ outline.setEmpty()
+ } else {
+ outline.setRoundRect(enforcedRectangle, enforcedCornerRadius)
+ }
+ }
+ }
+
+ private fun enforceRoundedCorners() {
+ if (enforcedCornerRadius <= 0) {
+ resetRoundedCorners()
+ return
+ }
+ val background: View? = RoundedCornerEnforcement.findBackground(this)
+ if (background == null || RoundedCornerEnforcement.hasAppWidgetOptedOut(this, background)) {
+ resetRoundedCorners()
+ return
+ }
+ RoundedCornerEnforcement.computeRoundedRectangle(this, background, enforcedRectangle)
+ outlineProvider = cornerRadiusEnforcementOutline
+ clipToOutline = true
+ invalidateOutline()
+ }
+
+ private fun resetRoundedCorners() {
+ outlineProvider = ViewOutlineProvider.BACKGROUND
+ clipToOutline = false
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt
new file mode 100644
index 000000000000..abda44be09fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/RoundedCornerEnforcement.kt
@@ -0,0 +1,154 @@
+/*
+ * 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.widgets
+
+import android.annotation.IdRes
+import android.annotation.Nullable
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.Rect
+import android.view.View
+import android.view.ViewGroup
+import androidx.core.os.BuildCompat.isAtLeastS
+import com.android.systemui.res.R
+import kotlin.math.min
+
+/**
+ * Utilities to compute the enforced use of rounded corners on App Widgets. This is a fork of the
+ * Launcher3 source code to enforce the same visual treatment on communal hub.
+ */
+internal object RoundedCornerEnforcement {
+ /**
+ * Find the background view for a widget.
+ *
+ * @param appWidget the view containing the App Widget (typically the instance of
+ * [CommunalAppWidgetHostView]).
+ */
+ fun findBackground(appWidget: View): View? {
+ val backgrounds = findViewsWithId(appWidget, R.id.background)
+ if (backgrounds.size == 1) {
+ return backgrounds[0]
+ }
+ // Really, the argument should contain the widget, so it cannot be the background.
+ if (appWidget is ViewGroup) {
+ val vg = appWidget
+ if (vg.childCount > 0) {
+ return findUndefinedBackground(vg.getChildAt(0))
+ }
+ }
+ return appWidget
+ }
+
+ /** Check whether the app widget has opted out of the enforcement. */
+ fun hasAppWidgetOptedOut(appWidget: View?, background: View): Boolean {
+ return background.id == R.id.background && background.clipToOutline
+ }
+
+ /**
+ * Computes the rounded rectangle needed for this app widget.
+ *
+ * @param appWidget View onto which the rounded rectangle will be applied.
+ * @param background Background view. This must be either `appWidget` or a descendant of
+ * `appWidget`.
+ * @param outRect Rectangle set to the rounded rectangle coordinates, in the reference frame of
+ * `appWidget`.
+ */
+ fun computeRoundedRectangle(appWidget: View, background: View, outRect: Rect) {
+ var background = background
+ outRect.left = 0
+ outRect.right = background.width
+ outRect.top = 0
+ outRect.bottom = background.height
+ while (background !== appWidget) {
+ outRect.offset(background.left, background.top)
+ background = background.parent as View
+ }
+ }
+
+ /** Get the radius of the rounded rectangle defined in the host's resource. */
+ private fun getOwnedEnforcedRadius(context: Context): Float {
+ val res: Resources = context.resources
+ return res.getDimension(R.dimen.communal_enforced_rounded_corner_max_radius)
+ }
+
+ /**
+ * Computes the radius of the rounded rectangle that should be applied to a widget expanded in
+ * the given context.
+ */
+ fun computeEnforcedRadius(context: Context): Float {
+ if (!isAtLeastS()) {
+ return 0f
+ }
+ val res: Resources = context.resources
+ val systemRadius: Float =
+ res.getDimension(android.R.dimen.system_app_widget_background_radius)
+ val defaultRadius = getOwnedEnforcedRadius(context)
+ return min(defaultRadius, systemRadius)
+ }
+
+ private fun findViewsWithId(view: View, @IdRes viewId: Int): List<View> {
+ val output: MutableList<View> = ArrayList()
+ accumulateViewsWithId(view, viewId, output)
+ return output
+ }
+
+ // Traverse views. If the predicate returns true, continue on the children, otherwise, don't.
+ private fun accumulateViewsWithId(view: View, @IdRes viewId: Int, output: MutableList<View>) {
+ if (view.id == viewId) {
+ output.add(view)
+ return
+ }
+ if (view is ViewGroup) {
+ val vg = view
+ for (i in 0 until vg.childCount) {
+ accumulateViewsWithId(vg.getChildAt(i), viewId, output)
+ }
+ }
+ }
+
+ private fun isViewVisible(view: View): Boolean {
+ return if (view.visibility != View.VISIBLE) {
+ false
+ } else !view.willNotDraw() || view.foreground != null || view.background != null
+ }
+
+ @Nullable
+ private fun findUndefinedBackground(current: View): View? {
+ if (current.visibility != View.VISIBLE) {
+ return null
+ }
+ if (isViewVisible(current)) {
+ return current
+ }
+ var lastVisibleView: View? = null
+ // Find the first view that is either not a ViewGroup, or a ViewGroup which will draw
+ // something, or a ViewGroup that contains more than one view.
+ if (current is ViewGroup) {
+ val vg = current
+ for (i in 0 until vg.childCount) {
+ val visibleView = findUndefinedBackground(vg.getChildAt(i))
+ if (visibleView != null) {
+ if (lastVisibleView != null) {
+ return current // At least two visible children
+ }
+ lastVisibleView = visibleView
+ }
+ }
+ }
+ return lastVisibleView
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index b2d70523c282..6d9994fb2205 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -556,7 +556,7 @@ public class FrameworkServicesModule {
@Provides
@Singleton
static SubscriptionManager provideSubscriptionManager(Context context) {
- return context.getSystemService(SubscriptionManager.class);
+ return context.getSystemService(SubscriptionManager.class).createForAllUserProfiles();
}
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt
index 496c64e1120e..c6fb4f9d6956 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/KeyboardModule.kt
@@ -19,6 +19,8 @@ package com.android.systemui.keyboard
import com.android.systemui.keyboard.data.repository.KeyboardRepository
import com.android.systemui.keyboard.data.repository.KeyboardRepositoryImpl
+import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepository
+import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepositoryImpl
import dagger.Binds
import dagger.Module
@@ -27,4 +29,9 @@ abstract class KeyboardModule {
@Binds
abstract fun bindKeyboardRepository(repository: KeyboardRepositoryImpl): KeyboardRepository
+
+ @Binds
+ abstract fun bindStickyKeysRepository(
+ repository: StickyKeysRepositoryImpl
+ ): StickyKeysRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt
new file mode 100644
index 000000000000..37034f63aca7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/StickyKeysLogger.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.stickykeys
+
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.KeyboardLog
+import javax.inject.Inject
+
+private const val TAG = "stickyKeys"
+
+class StickyKeysLogger @Inject constructor(@KeyboardLog private val buffer: LogBuffer) {
+ fun logNewStickyKeysReceived(linkedHashMap: Map<ModifierKey, Locked>) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ { str1 = linkedHashMap.toString() },
+ { "new sticky keys state received: $str1" }
+ )
+ }
+} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
new file mode 100644
index 000000000000..34d288815570
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/data/repository/StickyKeysRepository.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.keyboard.stickykeys.data.repository
+
+import android.hardware.input.InputManager
+import android.hardware.input.InputManager.StickyModifierStateListener
+import android.hardware.input.StickyModifierState
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+import javax.inject.Inject
+
+interface StickyKeysRepository {
+ val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>>
+ val settingEnabled: Flow<Boolean>
+}
+
+class StickyKeysRepositoryImpl
+@Inject
+constructor(
+ private val inputManager: InputManager,
+ @Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val stickyKeysLogger: StickyKeysLogger,
+) : StickyKeysRepository {
+
+ override val stickyKeys: Flow<LinkedHashMap<ModifierKey, Locked>> =
+ conflatedCallbackFlow {
+ val listener = StickyModifierStateListener { stickyModifierState ->
+ trySendWithFailureLogging(stickyModifierState, TAG)
+ }
+ // after registering, InputManager calls listener with the current value
+ inputManager.registerStickyModifierStateListener(Runnable::run, listener)
+ awaitClose { inputManager.unregisterStickyModifierStateListener(listener) }
+ }
+ .map { toStickyKeysMap(it) }
+ .onEach { stickyKeysLogger.logNewStickyKeysReceived(it) }
+ .flowOn(backgroundDispatcher)
+
+ // TODO(b/319837892): Implement reading actual setting
+ override val settingEnabled: StateFlow<Boolean> = MutableStateFlow(true)
+
+ private fun toStickyKeysMap(state: StickyModifierState): LinkedHashMap<ModifierKey, Locked> {
+ val keys = linkedMapOf<ModifierKey, Locked>()
+ state.apply {
+ if (isAltGrModifierOn) keys[ALT_GR] = Locked(false)
+ if (isAltGrModifierLocked) keys[ALT_GR] = Locked(true)
+ if (isAltModifierOn) keys[ALT] = Locked(false)
+ if (isAltModifierLocked) keys[ALT] = Locked(true)
+ if (isCtrlModifierOn) keys[CTRL] = Locked(false)
+ if (isCtrlModifierLocked) keys[CTRL] = Locked(true)
+ if (isMetaModifierOn) keys[META] = Locked(false)
+ if (isMetaModifierLocked) keys[META] = Locked(true)
+ if (isShiftModifierOn) keys[SHIFT] = Locked(false)
+ if (isShiftModifierLocked) keys[SHIFT] = Locked(true)
+ }
+ return keys
+ }
+
+ companion object {
+ const val TAG = "StickyKeysRepositoryImpl"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.kt
new file mode 100644
index 000000000000..d5f082a2566f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/shared/model/StickyKey.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.keyboard.stickykeys.shared.model
+
+@JvmInline
+value class Locked(val locked: Boolean)
+
+enum class ModifierKey(val text: String) {
+ ALT("ALT LEFT"),
+ ALT_GR("ALT RIGHT"),
+ CTRL("CTRL"),
+ META("META"),
+ SHIFT("SHIFT"),
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt
new file mode 100644
index 000000000000..26eb706da200
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModel.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyboard.stickykeys.ui.viewmodel
+
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepository
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
+import javax.inject.Inject
+
+class StickyKeysIndicatorViewModel
+@Inject
+constructor(
+ stickyKeysRepository: StickyKeysRepository,
+ keyboardRepository: KeyboardRepository,
+ @Application applicationScope: CoroutineScope,
+) {
+
+ @OptIn(ExperimentalCoroutinesApi::class)
+ val indicatorContent: Flow<Map<ModifierKey, Locked>> =
+ keyboardRepository.isAnyKeyboardConnected
+ .flatMapLatest { keyboardPresent ->
+ if (keyboardPresent) stickyKeysRepository.settingEnabled else flowOf(false)
+ }
+ .flatMapLatest { enabled ->
+ if (enabled) stickyKeysRepository.stickyKeys else flowOf(emptyMap())
+ }
+ .stateIn(applicationScope, SharingStarted.Lazily, emptyMap())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
index b373f8520254..fede47957a7b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
@@ -53,14 +53,14 @@ interface KeyguardFaceAuthModule {
@SysUISingleton
@FaceAuthTableLog
fun provideFaceAuthTableLog(factory: TableLogBufferFactory): TableLogBuffer {
- return factory.create("FaceAuthTableLog", 100)
+ return factory.create("FaceAuthTableLog", 400)
}
@Provides
@SysUISingleton
@FaceDetectTableLog
fun provideFaceDetectTableLog(factory: TableLogBufferFactory): TableLogBuffer {
- return factory.create("FaceDetectTableLog", 100)
+ return factory.create("FaceDetectTableLog", 400)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 704ebdd40af6..d012d24b767e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -40,11 +40,13 @@ import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardDone
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -54,7 +56,10 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
/** Defines interface for classes that encapsulate application state for the keyguard. */
@@ -208,6 +213,12 @@ interface KeyguardRepository {
val clockShouldBeCentered: Flow<Boolean>
/**
+ * Whether the primary authentication is required for the given user due to lockdown or
+ * encryption after reboot.
+ */
+ val isEncryptedOrLockdown: Flow<Boolean>
+
+ /**
* Returns `true` if the keyguard is showing; `false` otherwise.
*
* Note: this is also `true` when the lock-screen is occluded with an `Activity` "above" it in
@@ -279,6 +290,7 @@ constructor(
@Application private val scope: CoroutineScope,
private val systemClock: SystemClock,
facePropertyRepository: FacePropertyRepository,
+ private val userTracker: UserTracker,
) : KeyguardRepository {
private val _dismissAction: MutableStateFlow<DismissAction> =
MutableStateFlow(DismissAction.None)
@@ -544,6 +556,24 @@ constructor(
awaitClose { dozeTransitionListener.removeCallback(callback) }
}
+ @OptIn(ExperimentalCoroutinesApi::class)
+ override val isEncryptedOrLockdown: Flow<Boolean> =
+ conflatedCallbackFlow {
+ val callback =
+ object : KeyguardUpdateMonitorCallback() {
+ override fun onStrongAuthStateChanged(userId: Int) {
+ trySend(userId)
+ }
+ }
+
+ keyguardUpdateMonitor.registerCallback(callback)
+
+ awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+ }
+ .filter { userId -> userId == userTracker.userId }
+ .onStart { emit(userTracker.userId) }
+ .mapLatest { userId -> keyguardUpdateMonitor.isEncryptedOrLockdown(userId) }
+
override fun isKeyguardShowing(): Boolean {
return keyguardStateController.isShowing
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
new file mode 100644
index 000000000000..afe9151ac7a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepository.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import android.view.View
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+@SysUISingleton
+class KeyguardSmartspaceRepository @Inject constructor() {
+ private val _bcSmartspaceVisibility: MutableStateFlow<Int> = MutableStateFlow(View.GONE)
+ val bcSmartspaceVisibility: StateFlow<Int> = _bcSmartspaceVisibility.asStateFlow()
+
+ fun setBcSmartspaceVisibility(visibility: Int) {
+ _bcSmartspaceVisibility.value = visibility
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index fedd63be1454..8fa33ee7d0ca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -54,12 +54,33 @@ constructor(
) {
override fun start() {
- listenForAodToLockscreenOrOccluded()
+ listenForAodToLockscreen()
+ listenForAodToPrimaryBouncer()
listenForAodToGone()
+ listenForAodToOccluded()
listenForTransitionToCamera(scope, keyguardInteractor)
}
- private fun listenForAodToLockscreenOrOccluded() {
+ /**
+ * There are cases where the transition to AOD begins but never completes, such as tapping power
+ * during an incoming phone call when unlocked. In this case, GONE->AOD should be interrupted to
+ * run AOD->OCCLUDED.
+ */
+ private fun listenForAodToOccluded() {
+ scope.launch {
+ keyguardInteractor.isKeyguardOccluded.sample(startedKeyguardState, ::Pair).collect {
+ (isOccluded, startedKeyguardState) ->
+ if (isOccluded && startedKeyguardState == KeyguardState.AOD) {
+ startTransitionTo(
+ toState = KeyguardState.OCCLUDED,
+ modeOnCanceled = TransitionModeOnCanceled.RESET
+ )
+ }
+ }
+ }
+ }
+
+ private fun listenForAodToLockscreen() {
scope.launch {
keyguardInteractor
.dozeTransitionTo(DozeStateModel.FINISH)
@@ -72,20 +93,15 @@ constructor(
::toTriple
)
.collect { (_, lastStartedStep, occluded) ->
- if (lastStartedStep.to == KeyguardState.AOD) {
- val toState =
- if (occluded) KeyguardState.OCCLUDED else KeyguardState.LOCKSCREEN
+ if (lastStartedStep.to == KeyguardState.AOD && !occluded) {
val modeOnCanceled =
- if (
- toState == KeyguardState.LOCKSCREEN &&
- lastStartedStep.from == KeyguardState.LOCKSCREEN
- ) {
+ if (lastStartedStep.from == KeyguardState.LOCKSCREEN) {
TransitionModeOnCanceled.REVERSE
} else {
TransitionModeOnCanceled.LAST_VALUE
}
startTransitionTo(
- toState = toState,
+ toState = KeyguardState.LOCKSCREEN,
modeOnCanceled = modeOnCanceled,
)
}
@@ -93,11 +109,26 @@ constructor(
}
}
+ /**
+ * If there is a biometric lockout and FPS is tapped while on AOD, it should go directly to the
+ * PRIMARY_BOUNCER.
+ */
+ private fun listenForAodToPrimaryBouncer() {
+ scope.launch {
+ keyguardInteractor.primaryBouncerShowing
+ .sample(startedKeyguardTransitionStep, ::Pair)
+ .collect { (isBouncerShowing, lastStartedTransitionStep) ->
+ if (isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.AOD) {
+ startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
+ }
+ }
+ }
+ }
+
private fun listenForAodToGone() {
scope.launch {
keyguardInteractor.biometricUnlockState.sample(finishedKeyguardState, ::Pair).collect {
- pair ->
- val (biometricUnlockState, keyguardState) = pair
+ (biometricUnlockState, keyguardState) ->
if (keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)) {
startTransitionTo(KeyguardState.GONE)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 6eb3b64d4c09..6170356d4a90 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -264,6 +264,12 @@ constructor(
}
}
+ /**
+ * Whether the primary authentication is required for the given user due to lockdown or
+ * encryption after reboot.
+ */
+ val isEncryptedOrLockdown: Flow<Boolean> = repository.isEncryptedOrLockdown
+
fun dozeTransitionTo(vararg states: DozeStateModel): Flow<DozeTransitionModel> {
return dozeTransitionModel.filter { states.contains(it.to) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt
new file mode 100644
index 000000000000..67b57456a5c6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSmartspaceInteractor.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardSmartspaceRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.StateFlow
+
+@SysUISingleton
+class KeyguardSmartspaceInteractor
+@Inject
+constructor(private val keyguardSmartspaceRepository: KeyguardSmartspaceRepository) {
+ var bcSmartspaceVisibility: StateFlow<Int> = keyguardSmartspaceRepository.bcSmartspaceVisibility
+
+ fun setBcSmartspaceVisibility(visibility: Int) {
+ keyguardSmartspaceRepository.setBcSmartspaceVisibility(visibility)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index a8223ea83e1f..b43ab5e9110d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -37,17 +37,19 @@ import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.shareIn
/** Encapsulates business-logic related to the keyguard transitions. */
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class KeyguardTransitionInteractor
@Inject
@@ -192,29 +194,121 @@ constructor(
val finishedKeyguardTransitionStep: Flow<TransitionStep> =
repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
- /** The destination state of the last started transition. */
+ /** The destination state of the last [TransitionState.STARTED] transition. */
val startedKeyguardState: SharedFlow<KeyguardState> =
startedKeyguardTransitionStep
.map { step -> step.to }
.shareIn(scope, SharingStarted.Eagerly, replay = 1)
- /** The last completed [KeyguardState] transition */
+ /**
+ * The last [KeyguardState] to which we [TransitionState.FINISHED] a transition.
+ *
+ * WARNING: This will NOT emit a value if a transition is CANCELED, and will also not emit a
+ * value when a subsequent transition is STARTED. It will *only* emit once we have finally
+ * FINISHED in a state. This can have unintuitive implications.
+ *
+ * For example, if we're transitioning from GONE -> DOZING, and that transition is CANCELED in
+ * favor of a DOZING -> LOCKSCREEN transition, the FINISHED state is still GONE, and will remain
+ * GONE throughout the DOZING -> LOCKSCREEN transition until the DOZING -> LOCKSCREEN transition
+ * finishes (at which point we'll be FINISHED in LOCKSCREEN).
+ *
+ * Since there's no real limit to how many consecutive transitions can be canceled, it's even
+ * possible for the FINISHED state to be the same as the STARTED state while still
+ * transitioning.
+ *
+ * For example:
+ * 1. We're finished in GONE.
+ * 2. The user presses the power button, starting a GONE -> DOZING transition. We're still
+ * FINISHED in GONE.
+ * 3. The user changes their mind, pressing the power button to wake up; this starts a DOZING ->
+ * LOCKSCREEN transition. We're still FINISHED in GONE.
+ * 4. The user quickly swipes away the lockscreen prior to DOZING -> LOCKSCREEN finishing; this
+ * starts a LOCKSCREEN -> GONE transition. We're still FINISHED in GONE, but we've also
+ * STARTED a transition *to* GONE.
+ * 5. We'll emit KeyguardState.GONE again once the transition finishes.
+ *
+ * If you just need to know when we eventually settle into a state, this flow is likely
+ * sufficient. However, if you're having issues with state *during* transitions started after
+ * one or more canceled transitions, you probably need to use [currentKeyguardState].
+ */
val finishedKeyguardState: SharedFlow<KeyguardState> =
finishedKeyguardTransitionStep
.map { step -> step.to }
.shareIn(scope, SharingStarted.Eagerly, replay = 1)
/**
- * Whether we're currently in a transition to a new [KeyguardState] and haven't yet completed
- * it.
+ * The [KeyguardState] we're currently in.
+ *
+ * If we're not in transition, this is simply the [finishedKeyguardState]. If we're in
+ * transition, this is the state we're transitioning *from*.
+ *
+ * Absent CANCELED transitions, [currentKeyguardState] and [finishedKeyguardState] are always
+ * identical - if a transition FINISHES in a given state, the subsequent state we START a
+ * transition *from* would always be that same previously FINISHED state.
+ *
+ * However, if a transition is CANCELED, the next transition will START from a state we never
+ * FINISHED in. For example, if we transition from GONE -> DOZING, but CANCEL that transition in
+ * favor of DOZING -> LOCKSCREEN, we've STARTED a transition *from* DOZING despite never
+ * FINISHING in DOZING. Thus, the current state will be DOZING but the FINISHED state will still
+ * be GONE.
+ *
+ * In this example, if there was DOZING-related state that needs to be set up in order to
+ * properly render a DOZING -> LOCKSCREEN transition, it would never be set up if we were
+ * listening for [finishedKeyguardState] to emit DOZING. However, [currentKeyguardState] would
+ * emit DOZING immediately upon STARTING DOZING -> LOCKSCREEN, allowing us to set up the state.
+ *
+ * Whether you want to use [currentKeyguardState] or [finishedKeyguardState] depends on your
+ * specific use case and how you want to handle cancellations. In general, if you're dealing
+ * with state/UI present across multiple [KeyguardState]s, you probably want
+ * [currentKeyguardState]. If you're dealing with state/UI encapsulated within a single state,
+ * you likely want [finishedKeyguardState].
+ *
+ * As an example, let's say you want to animate in a message on the lockscreen UI after waking
+ * up, and that TextView is not involved in animations between states. You'd want to collect
+ * [finishedKeyguardState], so you'll only animate it in once we're settled on the lockscreen.
+ * If you use [currentKeyguardState] in this case, a DOZING -> LOCKSCREEN transition that is
+ * interrupted by a LOCKSCREEN -> GONE transition would cause the message to become visible
+ * immediately upon LOCKSCREEN -> GONE STARTING, as the current state would become LOCKSCREEN in
+ * that case. That's likely not what you want.
+ *
+ * On the other hand, let's say you're animating the smartspace from alpha 0f to 1f during
+ * DOZING -> LOCKSCREEN, but the transition is interrupted by LOCKSCREEN -> GONE. LS -> GONE
+ * needs the smartspace to be alpha=1f so that it can play the shared-element unlock animation.
+ * In this case, we'd want to collect [currentKeyguardState] and ensure the smartspace is
+ * visible when the current state is LOCKSCREEN. If you use [finishedKeyguardState] in this
+ * case, the smartspace will never be set to alpha = 1f and you'll have a half-faded smartspace
+ * during the LS -> GONE transition.
+ *
+ * If you need special-case handling for cancellations (such as conditional handling depending
+ * on which [KeyguardState] was canceled) you can collect [canceledKeyguardTransitionStep]
+ * directly.
+ *
+ * As a helpful footnote, here's the values of [finishedKeyguardState] and
+ * [currentKeyguardState] during a sequence with two cancellations:
+ * 1. We're FINISHED in GONE. currentKeyguardState=GONE; finishedKeyguardState=GONE.
+ * 2. We START a transition from GONE -> DOZING. currentKeyguardState=GONE;
+ * finishedKeyguardState=GONE.
+ * 3. We CANCEL this transition and START a transition from DOZING -> LOCKSCREEN.
+ * currentKeyguardState=DOZING; finishedKeyguardState=GONE.
+ * 4. We subsequently also CANCEL DOZING -> LOCKSCREEN and START LOCKSCREEN -> GONE.
+ * currentKeyguardState=LOCKSCREEN finishedKeyguardState=GONE.
+ * 5. LOCKSCREEN -> GONE is allowed to FINISH. currentKeyguardState=GONE;
+ * finishedKeyguardState=GONE.
*/
- val isInTransitionToAnyState =
- combine(
- startedKeyguardTransitionStep,
- finishedKeyguardState,
- ) { startedStep, finishedState ->
- startedStep.to != finishedState
- }
+ val currentKeyguardState: SharedFlow<KeyguardState> =
+ repository.transitions
+ .mapLatest {
+ if (it.transitionState == TransitionState.FINISHED) {
+ it.to
+ } else {
+ it.from
+ }
+ }
+ .distinctUntilChanged()
+ .shareIn(scope, SharingStarted.Eagerly, replay = 1)
+
+ /** Whether we've currently STARTED a transition and haven't yet FINISHED it. */
+ val isInTransitionToAnyState = isInTransitionWhere({ true }, { true })
/**
* The amount of transition into or out of the given [KeyguardState].
@@ -304,13 +398,12 @@ constructor(
fromStatePredicate: (KeyguardState) -> Boolean,
toStatePredicate: (KeyguardState) -> Boolean,
): Flow<Boolean> {
- return combine(
- startedKeyguardTransitionStep,
- finishedKeyguardState,
- ) { startedStep, finishedState ->
- fromStatePredicate(startedStep.from) &&
- toStatePredicate(startedStep.to) &&
- finishedState != startedStep.to
+ return repository.transitions
+ .filter { it.transitionState != TransitionState.CANCELED }
+ .mapLatest {
+ it.transitionState != TransitionState.FINISHED &&
+ fromStatePredicate(it.from) &&
+ toStatePredicate(it.to)
}
.distinctUntilChanged()
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index bf763b4e1f99..400b8bfff9b0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -30,7 +30,10 @@ import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.animation.Interpolators
+import com.android.keyguard.KeyguardClockSwitch.LARGE
+import com.android.keyguard.KeyguardClockSwitch.SMALL
import com.android.systemui.Flags.migrateClocksToBlueprint
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
@@ -48,6 +51,7 @@ object KeyguardClockViewBinder {
keyguardRootView: ConstraintLayout,
viewModel: KeyguardClockViewModel,
keyguardClockInteractor: KeyguardClockInteractor,
+ blueprintInteractor: KeyguardBlueprintInteractor,
) {
keyguardRootView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
@@ -61,18 +65,16 @@ object KeyguardClockViewBinder {
viewModel.currentClock.collect { currentClock ->
cleanupClockViews(viewModel.clock, keyguardRootView, viewModel.burnInLayer)
viewModel.clock = currentClock
- addClockViews(currentClock, keyguardRootView, viewModel.burnInLayer)
- viewModel.burnInLayer?.updatePostLayout(keyguardRootView)
- applyConstraints(clockSection, keyguardRootView, true)
+ addClockViews(currentClock, keyguardRootView)
+ updateBurnInLayer(keyguardRootView, viewModel)
+ blueprintInteractor.refreshBlueprint()
}
}
- // TODO: Weather clock dozing animation
- // will trigger both shouldBeCentered and clockSize change
- // we should avoid this
launch {
if (!migrateClocksToBlueprint()) return@launch
viewModel.clockSize.collect {
- applyConstraints(clockSection, keyguardRootView, true)
+ updateBurnInLayer(keyguardRootView, viewModel)
+ blueprintInteractor.refreshBlueprint()
}
}
launch {
@@ -82,7 +84,7 @@ object KeyguardClockViewBinder {
if (it.largeClock.config.hasCustomPositionUpdatedAnimation) {
playClockCenteringAnimation(clockSection, keyguardRootView, it)
} else {
- applyConstraints(clockSection, keyguardRootView, true)
+ blueprintInteractor.refreshBlueprint()
}
}
}
@@ -90,6 +92,29 @@ object KeyguardClockViewBinder {
}
}
}
+ @VisibleForTesting
+ fun updateBurnInLayer(
+ keyguardRootView: ConstraintLayout,
+ viewModel: KeyguardClockViewModel,
+ ) {
+ val burnInLayer = viewModel.burnInLayer
+ val clockController = viewModel.currentClock.value
+ clockController?.let { clock ->
+ when (viewModel.clockSize.value) {
+ LARGE -> {
+ clock.smallClock.layout.views.forEach { burnInLayer?.removeView(it) }
+ if (clock.config.useAlternateSmartspaceAODTransition) {
+ clock.largeClock.layout.views.forEach { burnInLayer?.addView(it) }
+ }
+ }
+ SMALL -> {
+ clock.smallClock.layout.views.forEach { burnInLayer?.addView(it) }
+ clock.largeClock.layout.views.forEach { burnInLayer?.removeView(it) }
+ }
+ }
+ }
+ viewModel.burnInLayer?.updatePostLayout(keyguardRootView)
+ }
private fun cleanupClockViews(
clockController: ClockController?,
@@ -116,7 +141,6 @@ object KeyguardClockViewBinder {
fun addClockViews(
clockController: ClockController?,
rootView: ConstraintLayout,
- burnInLayer: Layer?
) {
clockController?.let { clock ->
clock.smallClock.layout.views[0].id = R.id.lockscreen_clock_view
@@ -125,17 +149,10 @@ object KeyguardClockViewBinder {
}
// small clock should either be a single view or container with id
// `lockscreen_clock_view`
- clock.smallClock.layout.views.forEach {
- rootView.addView(it)
- burnInLayer?.addView(it)
- }
+ clock.smallClock.layout.views.forEach { rootView.addView(it) }
clock.largeClock.layout.views.forEach { rootView.addView(it) }
- if (clock.config.useAlternateSmartspaceAODTransition) {
- clock.largeClock.layout.views.forEach { burnInLayer?.addView(it) }
- }
}
}
-
fun applyConstraints(
clockSection: ClockSection,
rootView: ConstraintLayout,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
index 81ce8f04d302..10392e3c1450 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -16,15 +16,13 @@
package com.android.systemui.keyguard.ui.binder
-import android.transition.TransitionManager
import android.view.View
import androidx.constraintlayout.helper.widget.Layer
import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.Flags.migrateClocksToBlueprint
-import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -35,10 +33,10 @@ import kotlinx.coroutines.launch
object KeyguardSmartspaceViewBinder {
@JvmStatic
fun bind(
- smartspaceSection: SmartspaceSection,
keyguardRootView: ConstraintLayout,
clockViewModel: KeyguardClockViewModel,
smartspaceViewModel: KeyguardSmartspaceViewModel,
+ blueprintInteractor: KeyguardBlueprintInteractor,
) {
keyguardRootView.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -46,22 +44,56 @@ object KeyguardSmartspaceViewBinder {
if (!migrateClocksToBlueprint()) return@launch
clockViewModel.hasCustomWeatherDataDisplay.collect { hasCustomWeatherDataDisplay
->
- if (hasCustomWeatherDataDisplay) {
- removeDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel)
- } else {
- addDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel)
- }
- clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView)
- val constraintSet = ConstraintSet().apply { clone(keyguardRootView) }
- smartspaceSection.applyConstraints(constraintSet)
- TransitionManager.beginDelayedTransition(keyguardRootView)
- constraintSet.applyTo(keyguardRootView)
+ updateDateWeatherToBurnInLayer(
+ keyguardRootView,
+ clockViewModel,
+ smartspaceViewModel
+ )
+ blueprintInteractor.refreshBlueprint()
+ }
+ }
+
+ launch {
+ smartspaceViewModel.bcSmartspaceVisibility.collect {
+ updateBCSmartspaceInBurnInLayer(keyguardRootView, clockViewModel)
+ blueprintInteractor.refreshBlueprint()
}
}
}
}
}
+ private fun updateBCSmartspaceInBurnInLayer(
+ keyguardRootView: ConstraintLayout,
+ clockViewModel: KeyguardClockViewModel,
+ ) {
+ // Visibility is controlled by updateTargetVisibility in CardPagerAdapter
+ val burnInLayer = keyguardRootView.requireViewById<Layer>(R.id.burn_in_layer)
+ burnInLayer.apply {
+ val smartspaceView =
+ keyguardRootView.requireViewById<View>(sharedR.id.bc_smartspace_view)
+ if (smartspaceView.visibility == View.VISIBLE) {
+ addView(smartspaceView)
+ } else {
+ removeView(smartspaceView)
+ }
+ }
+ clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView)
+ }
+
+ private fun updateDateWeatherToBurnInLayer(
+ keyguardRootView: ConstraintLayout,
+ clockViewModel: KeyguardClockViewModel,
+ smartspaceViewModel: KeyguardSmartspaceViewModel
+ ) {
+ if (clockViewModel.hasCustomWeatherDataDisplay.value) {
+ removeDateWeatherFromBurnInLayer(keyguardRootView, smartspaceViewModel)
+ } else {
+ addDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel)
+ }
+ clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView)
+ }
+
private fun addDateWeatherToBurnInLayer(
constraintLayout: ConstraintLayout,
smartspaceViewModel: KeyguardSmartspaceViewModel
@@ -76,13 +108,13 @@ object KeyguardSmartspaceViewBinder {
constraintLayout.requireViewById<View>(sharedR.id.date_smartspace_view)
val weatherView =
constraintLayout.requireViewById<View>(sharedR.id.weather_smartspace_view)
- addView(weatherView)
addView(dateView)
+ addView(weatherView)
}
}
}
- private fun removeDateWeatherToBurnInLayer(
+ private fun removeDateWeatherFromBurnInLayer(
constraintLayout: ConstraintLayout,
smartspaceViewModel: KeyguardSmartspaceViewModel
) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
index 24d06026dcf7..8472a9f6da6d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
@@ -23,6 +23,7 @@ import com.android.systemui.keyguard.shared.model.KeyguardBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodBurnInSection
import com.android.systemui.keyguard.ui.view.layout.sections.AodNotificationIconsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.ClockSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultDeviceEntrySection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultIndicationAreaSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
@@ -30,11 +31,10 @@ import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSec
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule
-import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeClockSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeMediaSection
import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeNotificationStackScrollLayoutSection
-import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeSmartspaceSection
import com.android.systemui.util.kotlin.getOrNull
import java.util.Optional
import javax.inject.Inject
@@ -62,8 +62,8 @@ constructor(
aodNotificationIconsSection: AodNotificationIconsSection,
aodBurnInSection: AodBurnInSection,
communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
- smartspaceSection: SplitShadeSmartspaceSection,
- clockSection: SplitShadeClockSection,
+ clockSection: ClockSection,
+ smartspaceSection: SmartspaceSection,
mediaSection: SplitShadeMediaSection,
) : KeyguardBlueprint {
override val id: String = ID
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
index d0626d58a4ad..fd530b77707a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
@@ -25,6 +25,8 @@ import android.transition.Visibility
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.helper.widget.Layer
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
+import com.android.systemui.res.R
class BaseBlueprintTransition : TransitionSet() {
init {
@@ -33,7 +35,16 @@ class BaseBlueprintTransition : TransitionSet() {
.addTransition(ChangeBounds())
.addTransition(AlphaInVisibility())
excludeTarget(Layer::class.java, /* exclude= */ true)
+ excludeClockAndSmartspaceViews()
}
+
+ private fun excludeClockAndSmartspaceViews() {
+ excludeTarget(R.id.lockscreen_clock_view, true)
+ excludeTarget(R.id.lockscreen_clock_view_large, true)
+ excludeTarget(SmartspaceView::class.java, true)
+ // TODO(b/319468190): need to exclude views from large weather clock
+ }
+
class AlphaOutVisibility : Visibility() {
override fun onDisappear(
sceneRoot: ViewGroup?,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInLayer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInLayer.kt
new file mode 100644
index 000000000000..67a20e588198
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInLayer.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import android.view.View
+import androidx.constraintlayout.helper.widget.Layer
+
+class AodBurnInLayer(context: Context) : Layer(context) {
+ // For setScale in Layer class, it stores it in mScaleX/Y and directly apply scale to
+ // referenceViews instead of keeping the value in fields of View class
+ // when we try to clone ConstraintSet, it will call getScaleX from View class and return 1.0
+ // and when we clone and apply, it will reset everything in the layer
+ // which cause the flicker from AOD to LS
+ private var _scaleX = 1F
+ private var _scaleY = 1F
+ // As described for _scaleX and _scaleY, we have similar issue with translation
+ private var _translationX = 1F
+ private var _translationY = 1F
+ // avoid adding views with same ids
+ override fun addView(view: View?) {
+ view?.let { if (it.id !in referencedIds) super.addView(view) }
+ }
+ override fun setScaleX(scaleX: Float) {
+ _scaleX = scaleX
+ super.setScaleX(scaleX)
+ }
+
+ override fun getScaleX(): Float {
+ return _scaleX
+ }
+
+ override fun setScaleY(scaleY: Float) {
+ _scaleY = scaleY
+ super.setScaleY(scaleY)
+ }
+
+ override fun getScaleY(): Float {
+ return _scaleY
+ }
+
+ override fun setTranslationX(dx: Float) {
+ _translationX = dx
+ super.setTranslationX(dx)
+ }
+
+ override fun getTranslationX(): Float {
+ return _translationX
+ }
+
+ override fun setTranslationY(dy: Float) {
+ _translationY = dy
+ super.setTranslationY(dy)
+ }
+
+ override fun getTranslationY(): Float {
+ return _translationY
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 1ccc6ccf2cec..3d36eb03a1bc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -19,16 +19,13 @@ package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.Context
import android.view.View
-import androidx.constraintlayout.helper.widget.Layer
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
-import com.android.systemui.shared.R as sharedR
import javax.inject.Inject
/** Adds a layer to group elements for translation for burn-in preventation */
@@ -37,10 +34,8 @@ class AodBurnInSection
constructor(
private val context: Context,
private val clockViewModel: KeyguardClockViewModel,
- private val smartspaceViewModel: KeyguardSmartspaceViewModel,
) : KeyguardSection() {
- lateinit var burnInLayer: Layer
-
+ private lateinit var burnInLayer: AodBurnInLayer
override fun addViews(constraintLayout: ConstraintLayout) {
if (!KeyguardShadeMigrationNssl.isEnabled) {
return
@@ -48,7 +43,7 @@ constructor(
val nic = constraintLayout.requireViewById<View>(R.id.aod_notification_icon_container)
burnInLayer =
- Layer(context).apply {
+ AodBurnInLayer(context).apply {
id = R.id.burn_in_layer
addView(nic)
if (!migrateClocksToBlueprint()) {
@@ -57,11 +52,6 @@ constructor(
addView(statusView)
}
}
- if (migrateClocksToBlueprint()) {
- // weather and date parts won't be added here, cause their visibility doesn't align
- // with others in burnInLayer
- addSmartspaceViews(constraintLayout)
- }
constraintLayout.addView(burnInLayer)
}
@@ -83,14 +73,4 @@ constructor(
override fun removeViews(constraintLayout: ConstraintLayout) {
constraintLayout.removeView(R.id.burn_in_layer)
}
-
- private fun addSmartspaceViews(constraintLayout: ConstraintLayout) {
- burnInLayer.apply {
- if (smartspaceViewModel.isSmartspaceEnabled) {
- val smartspaceView =
- constraintLayout.requireViewById<View>(sharedR.id.bc_smartspace_view)
- addView(smartspaceView)
- }
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index f560b5f068c9..ed7abff555e7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -30,9 +30,7 @@ import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
-import com.android.systemui.shared.R as sharedR
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDisplayNotificationIconViewStore
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
@@ -53,13 +51,13 @@ constructor(
private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
private val notificationIconAreaController: NotificationIconAreaController,
- private val smartspaceViewModel: KeyguardSmartspaceViewModel,
private val systemBarUtilsState: SystemBarUtilsState,
) : KeyguardSection() {
private var nicBindingDisposable: DisposableHandle? = null
private val nicId = R.id.aod_notification_icon_container
private lateinit var nic: NotificationIconContainer
+ private val smartSpaceBarrier = View.generateViewId()
override fun addViews(constraintLayout: ConstraintLayout) {
if (!KeyguardShadeMigrationNssl.isEnabled) {
@@ -118,7 +116,7 @@ constructor(
}
constraintSet.apply {
if (migrateClocksToBlueprint()) {
- connect(nicId, TOP, sharedR.id.bc_smartspace_view, BOTTOM, bottomMargin)
+ connect(nicId, TOP, R.id.smart_space_barrier_bottom, BOTTOM, bottomMargin)
setGoneMargin(nicId, BOTTOM, bottomMargin)
} else {
connect(nicId, TOP, R.id.keyguard_status_view, topAlignment, bottomMargin)
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 b5f32c8a5608..b344d3b9afea 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
@@ -23,11 +23,14 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.INVISIBLE
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.VISIBLE
import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
import com.android.systemui.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardClockViewBinder
@@ -35,8 +38,10 @@ import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceLayout
import com.android.systemui.res.R
+import com.android.systemui.shared.R as sharedR
import com.android.systemui.statusbar.policy.SplitShadeStateController
import com.android.systemui.util.Utils
+import dagger.Lazy
import javax.inject.Inject
internal fun ConstraintSet.setVisibility(
@@ -56,6 +61,7 @@ constructor(
protected val keyguardClockViewModel: KeyguardClockViewModel,
private val context: Context,
private val splitShadeStateController: SplitShadeStateController,
+ val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
) : KeyguardSection() {
override fun addViews(constraintLayout: ConstraintLayout) {}
@@ -68,6 +74,7 @@ constructor(
constraintLayout,
keyguardClockViewModel,
clockInteractor,
+ blueprintInteractor.get()
)
}
@@ -88,12 +95,16 @@ constructor(
): ConstraintSet {
// Add constraint between rootView and clockContainer
applyDefaultConstraints(constraintSet)
+ getNonTargetClockFace(clock).applyConstraints(constraintSet)
getTargetClockFace(clock).applyConstraints(constraintSet)
// Add constraint between elements in clock and clock container
return constraintSet.apply {
- setAlpha(getTargetClockFace(clock).views, 1F)
- setAlpha(getNonTargetClockFace(clock).views, 0F)
+ setVisibility(getTargetClockFace(clock).views, VISIBLE)
+ setVisibility(getNonTargetClockFace(clock).views, INVISIBLE)
+ if (!keyguardClockViewModel.useLargeClock) {
+ connect(sharedR.id.bc_smartspace_view, TOP, sharedR.id.date_smartspace_view, BOTTOM)
+ }
}
}
@@ -107,9 +118,12 @@ constructor(
private fun getLargeClockFace(clock: ClockController): ClockFaceLayout = clock.largeClock.layout
private fun getSmallClockFace(clock: ClockController): ClockFaceLayout = clock.smallClock.layout
open fun applyDefaultConstraints(constraints: ConstraintSet) {
+ val guideline =
+ if (keyguardClockViewModel.clockShouldBeCentered.value) PARENT_ID
+ else R.id.split_shade_guideline
constraints.apply {
connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START)
- connect(R.id.lockscreen_clock_view_large, END, PARENT_ID, END)
+ connect(R.id.lockscreen_clock_view_large, END, guideline, END)
connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
var largeClockTopMargin =
context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
index b0eee0a68b1f..8c5e9b4c6817 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.Context
+import android.view.View
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.END
@@ -27,11 +28,9 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
import com.android.systemui.scene.shared.flag.SceneContainerFlags
import com.android.systemui.shade.NotificationPanelView
-import com.android.systemui.shared.R as sharedR
import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator
@@ -54,7 +53,6 @@ constructor(
ambientState: AmbientState,
controller: NotificationStackScrollLayoutController,
notificationStackSizeCalculator: NotificationStackSizeCalculator,
- private val smartspaceViewModel: KeyguardSmartspaceViewModel,
@Main mainDispatcher: CoroutineDispatcher,
) :
NotificationStackScrollLayoutSection(
@@ -69,6 +67,7 @@ constructor(
notificationStackSizeCalculator,
mainDispatcher,
) {
+ private val smartSpaceBarrier = View.generateViewId()
override fun applyConstraints(constraintSet: ConstraintSet) {
if (!KeyguardShadeMigrationNssl.isEnabled) {
return
@@ -76,16 +75,14 @@ constructor(
constraintSet.apply {
val bottomMargin =
context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin)
-
if (migrateClocksToBlueprint()) {
connect(
R.id.nssl_placeholder,
TOP,
- sharedR.id.bc_smartspace_view,
+ R.id.smart_space_barrier_bottom,
BOTTOM,
bottomMargin
)
- setGoneMargin(R.id.nssl_placeholder, TOP, bottomMargin)
} else {
connect(R.id.nssl_placeholder, TOP, R.id.keyguard_status_view, BOTTOM, bottomMargin)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index 2a68f26d3ae7..0c0eb8a673a4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -24,6 +24,7 @@ import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
import androidx.constraintlayout.widget.ConstraintSet.LEFT
import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
import androidx.constraintlayout.widget.ConstraintSet.RIGHT
+import androidx.constraintlayout.widget.ConstraintSet.VISIBILITY_MODE_IGNORE
import com.android.systemui.Flags.keyguardBottomAreaRefactor
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
@@ -96,6 +97,11 @@ constructor(
constrainHeight(R.id.end_button, height)
connect(R.id.end_button, RIGHT, PARENT_ID, RIGHT, horizontalOffsetMargin)
connect(R.id.end_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin)
+
+ // The constraint set visibility for start and end button are default visible, set to
+ // ignore so the view's own initial visibility (invisible) is used
+ setVisibilityMode(R.id.start_button, VISIBILITY_MODE_IGNORE)
+ setVisibilityMode(R.id.end_button, VISIBILITY_MODE_IGNORE)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index eacd466bc473..37842a84c3d3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -18,37 +18,41 @@ package com.android.systemui.keyguard.ui.view.layout.sections
import android.content.Context
import android.view.View
+import android.view.View.GONE
+import android.view.ViewTreeObserver.OnGlobalLayoutListener
+import androidx.constraintlayout.widget.Barrier
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.END
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import androidx.constraintlayout.widget.ConstraintSet.START
-import androidx.constraintlayout.widget.ConstraintSet.TOP
-import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardSmartspaceViewBinder
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
-import com.android.systemui.res.R
+import com.android.systemui.shared.R
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
+import dagger.Lazy
import javax.inject.Inject
open class SmartspaceSection
@Inject
constructor(
+ val context: Context,
val keyguardClockViewModel: KeyguardClockViewModel,
val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
- private val context: Context,
+ val keyguardSmartspaceInteractor: KeyguardSmartspaceInteractor,
val smartspaceController: LockscreenSmartspaceController,
val keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
+ val blueprintInteractor: Lazy<KeyguardBlueprintInteractor>,
) : KeyguardSection() {
private var smartspaceView: View? = null
private var weatherView: View? = null
private var dateView: View? = null
+ private var smartspaceVisibilityListener: OnGlobalLayoutListener? = null
+
override fun addViews(constraintLayout: ConstraintLayout) {
if (!migrateClocksToBlueprint()) {
return
@@ -64,6 +68,20 @@ constructor(
}
}
keyguardUnlockAnimationController.lockscreenSmartspace = smartspaceView
+ smartspaceVisibilityListener =
+ object : OnGlobalLayoutListener {
+ var pastVisibility = GONE
+ override fun onGlobalLayout() {
+ smartspaceView?.let {
+ val newVisibility = it.visibility
+ if (pastVisibility != newVisibility) {
+ keyguardSmartspaceInteractor.setBcSmartspaceVisibility(newVisibility)
+ pastVisibility = newVisibility
+ }
+ }
+ }
+ }
+ smartspaceView?.viewTreeObserver?.addOnGlobalLayoutListener(smartspaceVisibilityListener)
}
override fun bindData(constraintLayout: ConstraintLayout) {
@@ -71,10 +89,10 @@ constructor(
return
}
KeyguardSmartspaceViewBinder.bind(
- this,
constraintLayout,
keyguardClockViewModel,
keyguardSmartspaceViewModel,
+ blueprintInteractor.get(),
)
}
@@ -82,65 +100,96 @@ constructor(
if (!migrateClocksToBlueprint()) {
return
}
- // Generally, weather should be next to dateView
- // smartspace should be below date & weather views
constraintSet.apply {
// migrate addDateWeatherView, addWeatherView from KeyguardClockSwitchController
- dateView?.let { dateView ->
- constrainHeight(dateView.id, WRAP_CONTENT)
- constrainWidth(dateView.id, WRAP_CONTENT)
- connect(
- dateView.id,
- START,
- PARENT_ID,
- START,
- context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start)
+ constrainHeight(R.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ constrainWidth(R.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ connect(
+ R.id.date_smartspace_view,
+ ConstraintSet.START,
+ ConstraintSet.PARENT_ID,
+ ConstraintSet.START,
+ context.resources.getDimensionPixelSize(
+ com.android.systemui.res.R.dimen.below_clock_padding_start
)
- }
- weatherView?.let {
- constrainWidth(it.id, WRAP_CONTENT)
- dateView?.let { dateView ->
- connect(it.id, TOP, dateView.id, TOP)
- connect(it.id, BOTTOM, dateView.id, BOTTOM)
- connect(it.id, START, dateView.id, END, 4)
- }
- }
+ )
+ constrainWidth(R.id.weather_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ connect(
+ R.id.weather_smartspace_view,
+ ConstraintSet.TOP,
+ R.id.date_smartspace_view,
+ ConstraintSet.TOP
+ )
+ connect(
+ R.id.weather_smartspace_view,
+ ConstraintSet.BOTTOM,
+ R.id.date_smartspace_view,
+ ConstraintSet.BOTTOM
+ )
+ connect(
+ R.id.weather_smartspace_view,
+ ConstraintSet.START,
+ R.id.date_smartspace_view,
+ ConstraintSet.END,
+ 4
+ )
+
// migrate addSmartspaceView from KeyguardClockSwitchController
- smartspaceView?.let {
- constrainHeight(it.id, WRAP_CONTENT)
- connect(
- it.id,
- START,
- PARENT_ID,
- START,
- context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start)
+ constrainHeight(R.id.bc_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ connect(
+ R.id.bc_smartspace_view,
+ ConstraintSet.START,
+ ConstraintSet.PARENT_ID,
+ ConstraintSet.START,
+ context.resources.getDimensionPixelSize(
+ com.android.systemui.res.R.dimen.below_clock_padding_start
)
- connect(
- it.id,
- END,
- PARENT_ID,
- END,
- context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end)
+ )
+ connect(
+ R.id.bc_smartspace_view,
+ ConstraintSet.END,
+ if (keyguardClockViewModel.clockShouldBeCentered.value) ConstraintSet.PARENT_ID
+ else com.android.systemui.res.R.id.split_shade_guideline,
+ ConstraintSet.END,
+ context.resources.getDimensionPixelSize(
+ com.android.systemui.res.R.dimen.below_clock_padding_end
)
- }
+ )
if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
- dateView?.let { dateView ->
- smartspaceView?.let { smartspaceView ->
- connect(dateView.id, BOTTOM, smartspaceView.id, TOP)
- }
- }
+ clear(R.id.date_smartspace_view, ConstraintSet.TOP)
+ connect(
+ R.id.date_smartspace_view,
+ ConstraintSet.BOTTOM,
+ R.id.bc_smartspace_view,
+ ConstraintSet.TOP
+ )
} else {
- dateView?.let { dateView ->
- clear(dateView.id, BOTTOM)
- connect(dateView.id, TOP, R.id.lockscreen_clock_view, BOTTOM)
- constrainHeight(dateView.id, WRAP_CONTENT)
- smartspaceView?.let { smartspaceView ->
- clear(smartspaceView.id, TOP)
- connect(smartspaceView.id, TOP, dateView.id, BOTTOM)
- }
- }
+ clear(R.id.date_smartspace_view, ConstraintSet.BOTTOM)
+ connect(
+ R.id.date_smartspace_view,
+ ConstraintSet.TOP,
+ com.android.systemui.res.R.id.lockscreen_clock_view,
+ ConstraintSet.BOTTOM
+ )
+ connect(
+ R.id.bc_smartspace_view,
+ ConstraintSet.TOP,
+ R.id.date_smartspace_view,
+ ConstraintSet.BOTTOM
+ )
}
+
+ createBarrier(
+ com.android.systemui.res.R.id.smart_space_barrier_bottom,
+ Barrier.BOTTOM,
+ 0,
+ *intArrayOf(
+ R.id.bc_smartspace_view,
+ R.id.date_smartspace_view,
+ R.id.weather_smartspace_view,
+ )
+ )
}
updateVisibility(constraintSet)
}
@@ -156,30 +205,28 @@ constructor(
}
}
}
+ smartspaceView?.viewTreeObserver?.removeOnGlobalLayoutListener(smartspaceVisibilityListener)
+ smartspaceVisibilityListener = null
}
private fun updateVisibility(constraintSet: ConstraintSet) {
constraintSet.apply {
- weatherView?.let {
- setVisibility(
- it.id,
- when (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
- true -> ConstraintSet.GONE
- false ->
- when (keyguardSmartspaceViewModel.isWeatherEnabled) {
- true -> ConstraintSet.VISIBLE
- false -> ConstraintSet.GONE
- }
- }
- )
- }
- dateView?.let {
- setVisibility(
- it.id,
- if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE
- else ConstraintSet.VISIBLE
- )
- }
+ setVisibility(
+ R.id.weather_smartspace_view,
+ when (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
+ true -> ConstraintSet.GONE
+ false ->
+ when (keyguardSmartspaceViewModel.isWeatherEnabled) {
+ true -> ConstraintSet.VISIBLE
+ false -> ConstraintSet.GONE
+ }
+ }
+ )
+ setVisibility(
+ R.id.date_smartspace_view,
+ if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) ConstraintSet.GONE
+ else ConstraintSet.VISIBLE
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt
deleted file mode 100644
index 19ba1aa4763a..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.view.layout.sections
-
-import android.content.Context
-import androidx.constraintlayout.widget.ConstraintSet
-import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.policy.SplitShadeStateController
-import javax.inject.Inject
-
-class SplitShadeClockSection
-@Inject
-constructor(
- clockInteractor: KeyguardClockInteractor,
- keyguardClockViewModel: KeyguardClockViewModel,
- context: Context,
- splitShadeStateController: SplitShadeStateController,
-) : ClockSection(clockInteractor, keyguardClockViewModel, context, splitShadeStateController) {
- override fun applyDefaultConstraints(constraints: ConstraintSet) {
- super.applyDefaultConstraints(constraints)
- val largeClockEndGuideline =
- if (keyguardClockViewModel.clockShouldBeCentered.value) ConstraintSet.PARENT_ID
- else R.id.split_shade_guideline
- constraints.apply {
- connect(
- R.id.lockscreen_clock_view_large,
- ConstraintSet.END,
- largeClockEndGuideline,
- ConstraintSet.END
- )
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
index f20ab06bcda9..b12a8a811955 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeMediaSection.kt
@@ -20,7 +20,6 @@ import android.content.Context
import android.view.View
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.FrameLayout
-import androidx.constraintlayout.widget.Barrier
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -31,11 +30,9 @@ import androidx.constraintlayout.widget.ConstraintSet.START
import androidx.constraintlayout.widget.ConstraintSet.TOP
import com.android.systemui.Flags.migrateClocksToBlueprint
import com.android.systemui.keyguard.shared.model.KeyguardSection
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.media.controls.ui.KeyguardMediaController
import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
-import com.android.systemui.shared.R as sharedR
import javax.inject.Inject
/** Aligns media on left side for split shade, below smartspace, date, and weather. */
@@ -44,11 +41,9 @@ class SplitShadeMediaSection
constructor(
private val context: Context,
private val notificationPanelView: NotificationPanelView,
- private val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
private val keyguardMediaController: KeyguardMediaController
) : KeyguardSection() {
private val mediaContainerId = R.id.status_view_media_container
- private val smartSpaceBarrier = R.id.smart_space_barrier_bottom
override fun addViews(constraintLayout: ConstraintLayout) {
if (!migrateClocksToBlueprint()) {
@@ -85,18 +80,7 @@ constructor(
constraintSet.apply {
constrainWidth(mediaContainerId, MATCH_CONSTRAINT)
constrainHeight(mediaContainerId, WRAP_CONTENT)
-
- createBarrier(
- smartSpaceBarrier,
- Barrier.BOTTOM,
- 0,
- *intArrayOf(
- sharedR.id.bc_smartspace_view,
- sharedR.id.date_smartspace_view,
- sharedR.id.weather_smartspace_view,
- )
- )
- connect(mediaContainerId, TOP, smartSpaceBarrier, BOTTOM)
+ connect(mediaContainerId, TOP, R.id.smart_space_barrier_bottom, BOTTOM)
connect(mediaContainerId, START, PARENT_ID, START)
connect(mediaContainerId, END, R.id.split_shade_guideline, END)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeSmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeSmartspaceSection.kt
deleted file mode 100644
index 8728adadd8c3..000000000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeSmartspaceSection.kt
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.view.layout.sections
-
-import android.content.Context
-import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
-import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
-import javax.inject.Inject
-
-/*
- * We need this class for the splitShadeBlueprint so `addViews` and `removeViews` will be called
- * when switching to and from splitShade.
- */
-class SplitShadeSmartspaceSection
-@Inject
-constructor(
- keyguardClockViewModel: KeyguardClockViewModel,
- keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
- context: Context,
- smartspaceController: LockscreenSmartspaceController,
- keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
-) :
- SmartspaceSection(
- keyguardClockViewModel,
- keyguardSmartspaceViewModel,
- context,
- smartspaceController,
- keyguardUnlockAnimationController,
- )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
index a1dd720a82f1..e8c1ab5feccc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
@@ -18,6 +18,7 @@ package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -33,6 +34,7 @@ constructor(
@Application applicationScope: CoroutineScope,
smartspaceController: LockscreenSmartspaceController,
keyguardClockViewModel: KeyguardClockViewModel,
+ smartspaceInteractor: KeyguardSmartspaceInteractor,
) {
/** Whether the smartspace section is available in the build. */
val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled()
@@ -78,4 +80,7 @@ constructor(
): Boolean {
return !clockIncludesCustomWeatherDisplay && isWeatherEnabled
}
+
+ /* trigger clock and smartspace constraints change when smartspace appears */
+ var bcSmartspaceVisibility: StateFlow<Int> = smartspaceInteractor.bcSmartspaceVisibility
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/OemSatelliteInputLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt
index 252945f1ed6a..5910701d9f2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/OemSatelliteInputLog.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/KeyboardLog.kt
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.dagger
+package com.android.systemui.log.dagger
-import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
import javax.inject.Qualifier
-/** Detailed [DeviceBasedSatelliteRepository] logs */
+/** A [com.android.systemui.log.LogBuffer] for keyboard-related functionality. */
@Qualifier
@MustBeDocumented
-@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
-annotation class OemSatelliteInputLog
+@Retention(AnnotationRetention.RUNTIME)
+annotation class KeyboardLog
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 24cb8fff9b67..3e0094081638 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -603,6 +603,14 @@ public class LogModule {
return factory.create("BluetoothTileDialogLog", 50);
}
+ /** Provides a {@link LogBuffer} for the keyboard functionalities. */
+ @Provides
+ @SysUISingleton
+ @KeyboardLog
+ public static LogBuffer provideKeyboardLogBuffer(LogBufferFactory factory) {
+ return factory.create("KeyboardLog", 50);
+ }
+
/** Provides a {@link LogBuffer} for {@link PackageChangeRepository} */
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 0d641ac9c688..58e042868607 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -624,7 +624,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
if (!mIsEnabled) {
- mGestureNavigationSettingsObserver.unregister();
+ mBackgroundExecutor.execute(mGestureNavigationSettingsObserver::unregister);
if (DEBUG_MISSING_GESTURE) {
Log.d(DEBUG_MISSING_GESTURE_TAG, "Unregister display listener");
}
@@ -642,7 +642,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
} else {
- mGestureNavigationSettingsObserver.register();
+ mBackgroundExecutor.execute(mGestureNavigationSettingsObserver::register);
updateDisplaySize();
if (DEBUG_MISSING_GESTURE) {
Log.d(DEBUG_MISSING_GESTURE_TAG, "Register display listener");
diff --git a/packages/SystemUI/src/com/android/systemui/qs/LeftRightArrowPressedListener.kt b/packages/SystemUI/src/com/android/systemui/qs/LeftRightArrowPressedListener.kt
new file mode 100644
index 000000000000..ca790e830f7f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/LeftRightArrowPressedListener.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.view.KeyEvent
+import android.view.View
+import androidx.core.util.Consumer
+
+/**
+ * Listens for left and right arrow keys pressed while focus is on the view.
+ *
+ * Key press is treated as correct when its full lifecycle happened on the view: first
+ * [KeyEvent.ACTION_DOWN] was performed, view didn't lose focus in the meantime and then
+ * [KeyEvent.ACTION_UP] was performed with the same [KeyEvent.getKeyCode]
+ */
+class LeftRightArrowPressedListener private constructor() :
+ View.OnKeyListener, View.OnFocusChangeListener {
+
+ private var lastKeyCode: Int? = 0
+ private var listener: Consumer<Int>? = null
+
+ fun setArrowKeyPressedListener(arrowPressedListener: Consumer<Int>) {
+ listener = arrowPressedListener
+ }
+
+ override fun onKey(view: View, keyCode: Int, keyEvent: KeyEvent): Boolean {
+ if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
+ // only scroll on ACTION_UP as we don't handle longpressing for now. Still we need
+ // to intercept even ACTION_DOWN otherwise keyboard focus will be moved before we
+ // have a chance to intercept ACTION_UP.
+ if (keyEvent.action == KeyEvent.ACTION_UP && keyCode == lastKeyCode) {
+ listener?.accept(keyCode)
+ lastKeyCode = null
+ } else if (keyEvent.repeatCount == 0) {
+ // we only read key events that are NOT coming from long pressing because that also
+ // causes reading ACTION_DOWN event (with repeated count > 0) when moving focus with
+ // arrow from another sibling view
+ lastKeyCode = keyCode
+ }
+ return true
+ }
+ return false
+ }
+
+ override fun onFocusChange(view: View, hasFocus: Boolean) {
+ // resetting lastKeyCode so we get fresh cleared state on focus
+ if (hasFocus) {
+ lastKeyCode = null
+ }
+ }
+
+ companion object {
+ @JvmStatic
+ fun createAndRegisterListenerForView(view: View): LeftRightArrowPressedListener {
+ val listener = LeftRightArrowPressedListener()
+ view.setOnKeyListener(listener)
+ view.onFocusChangeListener = listener
+ return listener
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
index 4770d5272508..6fb5174cc612 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PageIndicator.java
@@ -1,7 +1,11 @@
package com.android.systemui.qs;
+import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.LEFT;
+import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.RIGHT;
+
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Animatable2;
@@ -9,10 +13,12 @@ import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
+import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import com.android.settingslib.Utils;
@@ -36,13 +42,14 @@ public class PageIndicator extends ViewGroup {
private final ArrayList<Integer> mQueuedPositions = new ArrayList<>();
- private final int mPageIndicatorWidth;
- private final int mPageIndicatorHeight;
- private final int mPageDotWidth;
+ private int mPageIndicatorWidth;
+ private int mPageIndicatorHeight;
+ private int mPageDotWidth;
private @NonNull ColorStateList mTint;
private int mPosition = -1;
private boolean mAnimating;
+ private PageScrollActionListener mPageScrollActionListener;
private final Animatable2.AnimationCallback mAnimationCallback =
new Animatable2.AnimationCallback() {
@@ -77,6 +84,43 @@ public class PageIndicator extends ViewGroup {
mPageIndicatorWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_width);
mPageIndicatorHeight = res.getDimensionPixelSize(R.dimen.qs_page_indicator_height);
mPageDotWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_dot_width);
+ LeftRightArrowPressedListener arrowListener =
+ LeftRightArrowPressedListener.createAndRegisterListenerForView(this);
+ arrowListener.setArrowKeyPressedListener(keyCode -> {
+ if (mPageScrollActionListener != null) {
+ int swipeDirection = keyCode == KeyEvent.KEYCODE_DPAD_LEFT ? LEFT : RIGHT;
+ mPageScrollActionListener.onScrollActionTriggered(swipeDirection);
+ }
+ });
+ }
+
+ @Override
+ protected void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ updateResources();
+ }
+
+ private void updateResources() {
+ Resources res = getResources();
+ boolean changed = false;
+ int pageIndicatorWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_width);
+ if (pageIndicatorWidth != mPageIndicatorWidth) {
+ mPageIndicatorWidth = pageIndicatorWidth;
+ changed = true;
+ }
+ int pageIndicatorHeight = res.getDimensionPixelSize(R.dimen.qs_page_indicator_height);
+ if (pageIndicatorHeight != mPageIndicatorHeight) {
+ mPageIndicatorHeight = pageIndicatorHeight;
+ changed = true;
+ }
+ int pageIndicatorDotWidth = res.getDimensionPixelSize(R.dimen.qs_page_indicator_dot_width);
+ if (pageIndicatorDotWidth != mPageDotWidth) {
+ mPageDotWidth = pageIndicatorDotWidth;
+ changed = true;
+ }
+ if (changed) {
+ invalidate();
+ }
}
public void setNumPages(int numPages) {
@@ -280,4 +324,19 @@ public class PageIndicator extends ViewGroup {
getChildAt(i).layout(left, 0, mPageIndicatorWidth + left, mPageIndicatorHeight);
}
}
+
+ void setPageScrollActionListener(PageScrollActionListener listener) {
+ mPageScrollActionListener = listener;
+ }
+
+ interface PageScrollActionListener {
+
+ @IntDef({LEFT, RIGHT})
+ @interface Direction { }
+
+ int LEFT = 0;
+ int RIGHT = 1;
+
+ void onScrollActionTriggered(@Direction int swipeDirection);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 052c0daf0b56..43f3a2242da4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -1,6 +1,8 @@
package com.android.systemui.qs;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE;
+import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.LEFT;
+import static com.android.systemui.qs.PageIndicator.PageScrollActionListener.RIGHT;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -12,7 +14,6 @@ import android.content.Context;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.AttributeSet;
-import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -30,6 +31,7 @@ import androidx.viewpager.widget.ViewPager;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.PageIndicator.PageScrollActionListener.Direction;
import com.android.systemui.qs.QSPanel.QSTileLayout;
import com.android.systemui.qs.QSPanelControllerBase.TileRecord;
import com.android.systemui.qs.logging.QSLogger;
@@ -310,26 +312,18 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
mPageIndicator = indicator;
mPageIndicator.setNumPages(mPages.size());
mPageIndicator.setLocation(mPageIndicatorPosition);
- mPageIndicator.setOnKeyListener((view, keyCode, keyEvent) -> {
- if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
- // only scroll on ACTION_UP as we don't handle longpressing for now. Still we need
- // to intercept even ACTION_DOWN otherwise keyboard focus will be moved before we
- // have a chance to intercept ACTION_UP.
- if (keyEvent.getAction() == KeyEvent.ACTION_UP && mScroller.isFinished()) {
- scrollByX(getDeltaXForKeyboardScrolling(keyCode),
- SINGLE_PAGE_SCROLL_DURATION_MILLIS);
- }
- return true;
+ mPageIndicator.setPageScrollActionListener(swipeDirection -> {
+ if (mScroller.isFinished()) {
+ scrollByX(getDeltaXForPageScrolling(swipeDirection),
+ SINGLE_PAGE_SCROLL_DURATION_MILLIS);
}
- return false;
});
}
- private int getDeltaXForKeyboardScrolling(int keyCode) {
- if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && getCurrentItem() != 0) {
+ private int getDeltaXForPageScrolling(@Direction int swipeDirection) {
+ if (swipeDirection == LEFT && getCurrentItem() != 0) {
return -getWidth();
- } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
- && getCurrentItem() != mPages.size() - 1) {
+ } else if (swipeDirection == RIGHT && getCurrentItem() != mPages.size() - 1) {
return getWidth();
}
return 0;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
index c908e6e2afbe..5a872d699f35 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFooterView.java
@@ -35,6 +35,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.settingslib.development.DevelopmentSettingsEnabler;
+import com.android.systemui.FontSizeUtils;
import com.android.systemui.res.R;
/**
@@ -109,11 +110,31 @@ public class QSFooterView extends FrameLayout {
private void updateResources() {
updateFooterAnimator();
+ updateEditButtonResources();
+ updateBuildTextResources();
MarginLayoutParams lp = (MarginLayoutParams) getLayoutParams();
+ lp.height = getResources().getDimensionPixelSize(R.dimen.qs_footer_height);
+ int sideMargin = getResources().getDimensionPixelSize(R.dimen.qs_footer_margin);
+ lp.leftMargin = sideMargin;
+ lp.rightMargin = sideMargin;
lp.bottomMargin = getResources().getDimensionPixelSize(R.dimen.qs_footers_margin_bottom);
setLayoutParams(lp);
}
+ private void updateEditButtonResources() {
+ int size = getResources().getDimensionPixelSize(R.dimen.qs_footer_action_button_size);
+ int padding = getResources().getDimensionPixelSize(R.dimen.qs_footer_icon_padding);
+ MarginLayoutParams lp = (MarginLayoutParams) mEditButton.getLayoutParams();
+ lp.height = size;
+ lp.width = size;
+ mEditButton.setLayoutParams(lp);
+ mEditButton.setPadding(padding, padding, padding, padding);
+ }
+
+ private void updateBuildTextResources() {
+ FontSizeUtils.updateFontSizeFromStyle(mBuildText, R.style.TextAppearance_QS_Status_Build);
+ }
+
private void updateFooterAnimator() {
mFooterAnimator = createFooterAnimator();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index aff4a6759a47..2077d733172d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -88,9 +88,9 @@ class FooterActionsViewModel(
/** Called when the expansion of the Quick Settings changed. */
fun onQuickSettingsExpansionChanged(expansion: Float, isInSplitShade: Boolean) {
if (isInSplitShade) {
- // In split shade, we want to fade in the background only at the very end (see
- // b/240563302).
- val delay = 0.99f
+ // In split shade, we want to fade in the background when the QS background starts to
+ // show.
+ val delay = 0.15f
_alpha.value = expansion
_backgroundAlpha.value = max(0f, expansion - delay) / (1f - delay)
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
index 1805eb182cb1..1a06c38803af 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialog.kt
@@ -22,12 +22,14 @@ import android.view.LayoutInflater
import android.view.View
import android.view.View.AccessibilityDelegate
import android.view.View.GONE
+import android.view.View.INVISIBLE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.accessibility.AccessibilityNodeInfo
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction
import android.widget.ImageView
+import android.widget.ProgressBar
import android.widget.Switch
import android.widget.TextView
import androidx.recyclerview.widget.AsyncListDiffer
@@ -92,6 +94,8 @@ constructor(
private lateinit var pairNewDeviceButton: View
private lateinit var deviceListView: RecyclerView
private lateinit var scrollViewContent: View
+ private lateinit var progressBarAnimation: ProgressBar
+ private lateinit var progressBarBackground: View
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -122,6 +126,8 @@ constructor(
scrollViewContent = this
layoutParams.height = cachedContentHeight
}
+ progressBarAnimation = requireViewById(R.id.bluetooth_tile_dialog_progress_animation)
+ progressBarBackground = requireViewById(R.id.bluetooth_tile_dialog_progress_background)
}
override fun start() {
@@ -135,6 +141,17 @@ constructor(
super.dismiss()
}
+ internal suspend fun animateProgressBar(animate: Boolean) {
+ withContext(mainDispatcher) {
+ if (animate) {
+ showProgressBar()
+ } else {
+ delay(PROGRESS_BAR_ANIMATION_DURATION_MS)
+ hideProgressBar()
+ }
+ }
+ }
+
internal suspend fun onDeviceItemUpdated(
deviceItem: List<DeviceItem>,
showSeeAll: Boolean,
@@ -190,6 +207,28 @@ constructor(
}
}
+ private fun showProgressBar() {
+ if (
+ ::progressBarAnimation.isInitialized &&
+ ::progressBarBackground.isInitialized &&
+ progressBarAnimation.visibility != VISIBLE
+ ) {
+ progressBarAnimation.visibility = VISIBLE
+ progressBarBackground.visibility = INVISIBLE
+ }
+ }
+
+ private fun hideProgressBar() {
+ if (
+ ::progressBarAnimation.isInitialized &&
+ ::progressBarBackground.isInitialized &&
+ progressBarAnimation.visibility != INVISIBLE
+ ) {
+ progressBarAnimation.visibility = INVISIBLE
+ progressBarBackground.visibility = VISIBLE
+ }
+ }
+
internal inner class Adapter(private val onClickCallback: BluetoothTileDialogCallback) :
RecyclerView.Adapter<Adapter.DeviceItemViewHolder>() {
@@ -300,6 +339,7 @@ constructor(
const val ACTION_PAIR_NEW_DEVICE = "android.settings.BLUETOOTH_PAIRING_SETTINGS"
const val DISABLED_ALPHA = 0.3f
const val ENABLED_ALPHA = 1f
+ const val PROGRESS_BAR_ANIMATION_DURATION_MS = 1500L
private fun Boolean.toInt(): Int {
return if (this) 1 else 0
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
index 6d08f591690f..194e7bc955c9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModel.kt
@@ -105,22 +105,29 @@ constructor(
deviceItemInteractor.updateDeviceItems(context, DeviceFetchTrigger.FIRST_LOAD)
}
- bluetoothStateInteractor.bluetoothStateUpdate
- .filterNotNull()
+ // deviceItemUpdate is emitted when device item list is done fetching, update UI and
+ // stop the progress bar.
+ deviceItemInteractor.deviceItemUpdate
.onEach {
- dialog.onBluetoothStateUpdated(it, getSubtitleResId(it))
- updateDeviceItemJob?.cancel()
- updateDeviceItemJob = launch {
- deviceItemInteractor.updateDeviceItems(
- context,
- DeviceFetchTrigger.BLUETOOTH_STATE_CHANGE_RECEIVED
- )
+ updateDialogUiJob?.cancel()
+ updateDialogUiJob = launch {
+ dialog.apply {
+ onDeviceItemUpdated(
+ it.take(MAX_DEVICE_ITEM_ENTRY),
+ showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
+ showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
+ )
+ animateProgressBar(false)
+ }
}
}
.launchIn(this)
+ // deviceItemUpdateRequest is emitted when a bluetooth callback is called, re-fetch
+ // the device item list and animiate the progress bar.
deviceItemInteractor.deviceItemUpdateRequest
.onEach {
+ dialog.animateProgressBar(true)
updateDeviceItemJob?.cancel()
updateDeviceItemJob = launch {
deviceItemInteractor.updateDeviceItems(
@@ -131,27 +138,37 @@ constructor(
}
.launchIn(this)
- deviceItemInteractor.deviceItemUpdate
+ // bluetoothStateUpdate is emitted when bluetooth on/off state is changed, re-fetch
+ // the device item list.
+ bluetoothStateInteractor.bluetoothStateUpdate
+ .filterNotNull()
.onEach {
- updateDialogUiJob?.cancel()
- updateDialogUiJob = launch {
- dialog.onDeviceItemUpdated(
- it.take(MAX_DEVICE_ITEM_ENTRY),
- showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
- showPairNewDevice = bluetoothStateInteractor.isBluetoothEnabled
+ dialog.onBluetoothStateUpdated(it, getSubtitleResId(it))
+ updateDeviceItemJob?.cancel()
+ updateDeviceItemJob = launch {
+ deviceItemInteractor.updateDeviceItems(
+ context,
+ DeviceFetchTrigger.BLUETOOTH_STATE_CHANGE_RECEIVED
)
}
}
.launchIn(this)
+ // bluetoothStateToggle is emitted when user toggles the bluetooth state switch,
+ // send the new value to the bluetoothStateInteractor and animate the progress bar.
dialog.bluetoothStateToggle
- .onEach { bluetoothStateInteractor.isBluetoothEnabled = it }
+ .onEach {
+ dialog.animateProgressBar(true)
+ bluetoothStateInteractor.isBluetoothEnabled = it
+ }
.launchIn(this)
+ // deviceItemClick is emitted when user clicked on a device item.
dialog.deviceItemClick
.onEach { deviceItemInteractor.updateDeviceItemOnClick(it) }
.launchIn(this)
+ // contentHeight is emitted when the dialog is dismissed.
dialog.contentHeight
.onEach {
withContext(backgroundDispatcher) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
index a6cccf1cb41b..e2959fe834d4 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/WindowRootViewVisibilityInteractor.kt
@@ -24,7 +24,9 @@ import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.data.repository.WindowRootViewVisibilityRepository
import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.init.NotificationsController
+import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor
import com.android.systemui.statusbar.policy.HeadsUpManager
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -44,6 +46,7 @@ constructor(
private val keyguardRepository: KeyguardRepository,
private val headsUpManager: HeadsUpManager,
private val powerInteractor: PowerInteractor,
+ private val activeNotificationsInteractor: ActiveNotificationsInteractor,
) : CoreStartable {
private var notificationPresenter: NotificationPresenter? = null
@@ -117,6 +120,14 @@ constructor(
return if (headsUpManager.hasPinnedHeadsUp() && isNotifPresenterFullyCollapsed) {
1
} else {
+ getActiveNotificationsCount()
+ }
+ }
+
+ private fun getActiveNotificationsCount(): Int {
+ return if (NotificationsLiveDataStoreRefactor.isEnabled) {
+ activeNotificationsInteractor.allNotificationsCountValue
+ } else {
notificationsController?.getActiveNotificationsCount() ?: 0
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index ab08f660789c..a755805d1872 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -37,6 +37,7 @@ object SceneContainerFlag {
/** The flag description -- not an aconfig flag name */
const val DESCRIPTION = "SceneContainerFlag"
+ @JvmStatic
inline val isEnabled
get() =
SCENE_CONTAINER_ENABLED && // mainStaticFlag
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 4a839b8df9ba..93cfc5dbcbe3 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
@@ -85,12 +85,13 @@ object SceneWindowRootViewBinder {
view.addView(
ComposeFacade.createSceneContainerView(
- scope = this,
- context = view.context,
- viewModel = viewModel,
- windowInsets = windowInsets,
- sceneByKey = sortedSceneByKey,
- )
+ scope = this,
+ context = view.context,
+ viewModel = viewModel,
+ windowInsets = windowInsets,
+ sceneByKey = sortedSceneByKey,
+ )
+ .also { it.id = R.id.scene_container_root_composable }
)
val legacyView = view.requireViewById<View>(R.id.legacy_window_root)
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.kt
new file mode 100644
index 000000000000..b09bfe21e014
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerExt.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.settings
+
+import android.annotation.UserIdInt
+import android.content.Context
+import android.content.SharedPreferences
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+
+/** Extension functions for [UserFileManager]. */
+object UserFileManagerExt {
+
+ /** Returns a flow of [Unit] that is invoked each time the shared preference is updated. */
+ fun UserFileManager.observeSharedPreferences(
+ fileName: String,
+ @Context.PreferencesMode mode: Int,
+ @UserIdInt userId: Int
+ ): Flow<Unit> = conflatedCallbackFlow {
+ val sharedPrefs = getSharedPreferences(fileName, mode, userId)
+
+ val listener = SharedPreferences.OnSharedPreferenceChangeListener { _, _ -> trySend(Unit) }
+
+ sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
+ awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index d0da945ca7d0..9af2d58910b6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -20,15 +20,10 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
import com.android.systemui.media.controls.pipeline.MediaDataManager
-import com.android.systemui.media.controls.ui.MediaHierarchyManager
-import com.android.systemui.media.controls.ui.MediaHost
-import com.android.systemui.media.controls.ui.MediaHostState
-import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.qs.ui.adapter.QSSceneAdapter
import com.android.systemui.scene.shared.model.SceneKey
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import javax.inject.Inject
-import javax.inject.Named
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -46,7 +41,6 @@ constructor(
val shadeHeaderViewModel: ShadeHeaderViewModel,
val notifications: NotificationsPlaceholderViewModel,
val mediaDataManager: MediaDataManager,
- @Named(MediaModule.QUICK_QS_PANEL) private val mediaHost: MediaHost,
) {
/** The key of the scene we should switch to when swiping up. */
val upDestinationSceneKey: StateFlow<SceneKey> =
@@ -83,12 +77,6 @@ constructor(
}
}
- init {
- mediaHost.expansion = MediaHostState.EXPANDED
- mediaHost.showsOnlyActiveMedia = true
- mediaHost.init(MediaHierarchyManager.LOCATION_QQS)
- }
-
fun isMediaVisible(): Boolean {
// TODO(b/296122467): handle updates to carousel visibility while scene is still visible
return mediaDataManager.hasActiveMediaOrRecommendation()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
deleted file mode 100644
index cfbd015f1cd8..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ /dev/null
@@ -1,409 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-import android.view.accessibility.AccessibilityEvent;
-
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
-import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.time.SystemClock;
-
-import java.util.stream.Stream;
-
-/**
- * A manager which contains notification alerting functionality, providing methods to add and
- * remove notifications that appear on screen for a period of time and dismiss themselves at the
- * appropriate time. These include heads up notifications and ambient pulses.
- */
-public abstract class AlertingNotificationManager {
- private static final String TAG = "AlertNotifManager";
- protected final SystemClock mSystemClock;
- protected final ArrayMap<String, AlertEntry> mAlertEntries = new ArrayMap<>();
- protected final HeadsUpManagerLogger mLogger;
-
- protected int mMinimumDisplayTime;
- protected int mStickyForSomeTimeAutoDismissTime;
- protected int mAutoDismissTime;
- private DelayableExecutor mExecutor;
-
- public AlertingNotificationManager(HeadsUpManagerLogger logger,
- SystemClock systemClock, @Main DelayableExecutor executor) {
- mLogger = logger;
- mExecutor = executor;
- mSystemClock = systemClock;
- }
-
- /**
- * Called when posting a new notification that should alert the user and appear on screen.
- * Adds the notification to be managed.
- * @param entry entry to show
- */
- public void showNotification(@NonNull NotificationEntry entry) {
- mLogger.logShowNotification(entry);
- addAlertEntry(entry);
- updateNotification(entry.getKey(), true /* alert */);
- entry.setInterruption();
- }
-
- /**
- * Try to remove the notification. May not succeed if the notification has not been shown long
- * enough and needs to be kept around.
- * @param key the key of the notification to remove
- * @param releaseImmediately force a remove regardless of earliest removal time
- * @return true if notification is removed, false otherwise
- */
- public boolean removeNotification(@NonNull String key, boolean releaseImmediately) {
- mLogger.logRemoveNotification(key, releaseImmediately);
- AlertEntry alertEntry = mAlertEntries.get(key);
- if (alertEntry == null) {
- return true;
- }
- if (releaseImmediately || canRemoveImmediately(key)) {
- removeAlertEntry(key);
- } else {
- alertEntry.removeAsSoonAsPossible();
- return false;
- }
- return true;
- }
-
- /**
- * Called when the notification state has been updated.
- * @param key the key of the entry that was updated
- * @param alert whether the notification should alert again and force reevaluation of
- * removal time
- */
- public void updateNotification(@NonNull String key, boolean alert) {
- AlertEntry alertEntry = mAlertEntries.get(key);
- mLogger.logUpdateNotification(key, alert, alertEntry != null);
- if (alertEntry == null) {
- // the entry was released before this update (i.e by a listener) This can happen
- // with the groupmanager
- return;
- }
-
- alertEntry.mEntry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- if (alert) {
- alertEntry.updateEntry(true /* updatePostTime */, "updateNotification");
- }
- }
-
- /**
- * Clears all managed notifications.
- */
- public void releaseAllImmediately() {
- mLogger.logReleaseAllImmediately();
- // A copy is necessary here as we are changing the underlying map. This would cause
- // undefined behavior if we iterated over the key set directly.
- ArraySet<String> keysToRemove = new ArraySet<>(mAlertEntries.keySet());
- for (String key : keysToRemove) {
- removeAlertEntry(key);
- }
- }
-
- /**
- * Returns the entry if it is managed by this manager.
- * @param key key of notification
- * @return the entry
- */
- @Nullable
- public NotificationEntry getEntry(@NonNull String key) {
- AlertEntry entry = mAlertEntries.get(key);
- return entry != null ? entry.mEntry : null;
- }
-
- /**
- * Returns the stream of all current notifications managed by this manager.
- * @return all entries
- */
- @NonNull
- public Stream<NotificationEntry> getAllEntries() {
- return mAlertEntries.values().stream().map(headsUpEntry -> headsUpEntry.mEntry);
- }
-
- /**
- * Whether or not there are any active alerting notifications.
- * @return true if there is an alert, false otherwise
- */
- public boolean hasNotifications() {
- return !mAlertEntries.isEmpty();
- }
-
- /**
- * Whether or not the given notification is alerting and managed by this manager.
- * @return true if the notification is alerting
- */
- public boolean isAlerting(@NonNull String key) {
- return mAlertEntries.containsKey(key);
- }
-
- /**
- * Gets the flag corresponding to the notification content view this alert manager will show.
- *
- * @return flag corresponding to the content view
- */
- public abstract @InflationFlag int getContentFlag();
-
- /**
- * Add a new entry and begin managing it.
- * @param entry the entry to add
- */
- protected final void addAlertEntry(@NonNull NotificationEntry entry) {
- AlertEntry alertEntry = createAlertEntry();
- alertEntry.setEntry(entry);
- mAlertEntries.put(entry.getKey(), alertEntry);
- onAlertEntryAdded(alertEntry);
- entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- entry.setIsAlerting(true);
- }
-
- /**
- * Manager-specific logic that should occur when an entry is added.
- * @param alertEntry alert entry added
- */
- protected abstract void onAlertEntryAdded(@NonNull AlertEntry alertEntry);
-
- /**
- * Remove a notification and reset the alert entry.
- * @param key key of notification to remove
- */
- protected final void removeAlertEntry(@NonNull String key) {
- AlertEntry alertEntry = mAlertEntries.get(key);
- if (alertEntry == null) {
- return;
- }
- NotificationEntry entry = alertEntry.mEntry;
-
- // If the notification is animating, we will remove it at the end of the animation.
- if (entry != null && entry.isExpandAnimationRunning()) {
- return;
- }
- entry.demoteStickyHun();
- mAlertEntries.remove(key);
- onAlertEntryRemoved(alertEntry);
- entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
- alertEntry.reset();
- }
-
- /**
- * Manager-specific logic that should occur when an alert entry is removed.
- * @param alertEntry alert entry removed
- */
- protected abstract void onAlertEntryRemoved(@NonNull AlertEntry alertEntry);
-
- /**
- * Returns a new alert entry instance.
- * @return a new AlertEntry
- */
- protected AlertEntry createAlertEntry() {
- return new AlertEntry();
- }
-
- /**
- * Whether or not the alert can be removed currently. If it hasn't been on screen long enough
- * it should not be removed unless forced
- * @param key the key to check if removable
- * @return true if the alert entry can be removed
- */
- public boolean canRemoveImmediately(String key) {
- AlertEntry alertEntry = mAlertEntries.get(key);
- return alertEntry == null || alertEntry.wasShownLongEnough()
- || alertEntry.mEntry.isRowDismissed();
- }
-
- /**
- * @param key
- * @return true if the entry is (pinned and expanded) or (has an active remote input)
- */
- public boolean isSticky(String key) {
- AlertEntry alerting = mAlertEntries.get(key);
- if (alerting != null) {
- return alerting.isSticky();
- }
- return false;
- }
-
- /**
- * @param key
- * @return When a HUN entry should be removed in milliseconds from now
- */
- public long getEarliestRemovalTime(String key) {
- AlertEntry alerting = mAlertEntries.get(key);
- if (alerting != null) {
- return Math.max(0, alerting.mEarliestRemovalTime - mSystemClock.elapsedRealtime());
- }
- return 0;
- }
-
- protected class AlertEntry implements Comparable<AlertEntry> {
- @Nullable public NotificationEntry mEntry;
- public long mPostTime;
- public long mEarliestRemovalTime;
-
- @Nullable protected Runnable mRemoveAlertRunnable;
- @Nullable private Runnable mCancelRemoveAlertRunnable;
-
- public void setEntry(@NonNull final NotificationEntry entry) {
- setEntry(entry, () -> removeAlertEntry(entry.getKey()));
- }
-
- public void setEntry(@NonNull final NotificationEntry entry,
- @Nullable Runnable removeAlertRunnable) {
- mEntry = entry;
- mRemoveAlertRunnable = removeAlertRunnable;
-
- mPostTime = calculatePostTime();
- updateEntry(true /* updatePostTime */, "setEntry");
- }
-
- /**
- * Updates an entry's removal time.
- * @param updatePostTime whether or not to refresh the post time
- */
- public void updateEntry(boolean updatePostTime, @Nullable String reason) {
- mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
-
- final long now = mSystemClock.elapsedRealtime();
- mEarliestRemovalTime = now + mMinimumDisplayTime;
-
- if (updatePostTime) {
- mPostTime = Math.max(mPostTime, now);
- }
-
- if (isSticky()) {
- removeAutoRemovalCallbacks("updateEntry (sticky)");
- return;
- }
-
- final long finishTime = calculateFinishTime();
- final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
- scheduleAutoRemovalCallback(timeLeft, "updateEntry (not sticky)");
- }
-
- /**
- * Whether or not the notification is "sticky" i.e. should stay on screen regardless
- * of the timer (forever) and should be removed externally.
- * @return true if the notification is sticky
- */
- public boolean isSticky() {
- // This implementation is overridden by HeadsUpManager HeadsUpEntry #isSticky
- return false;
- }
-
- public boolean isStickyForSomeTime() {
- // This implementation is overridden by HeadsUpManager HeadsUpEntry #isStickyForSomeTime
- return false;
- }
-
- /**
- * Whether the notification has befen on screen long enough and can be removed.
- * @return true if the notification has been on screen long enough
- */
- public boolean wasShownLongEnough() {
- return mEarliestRemovalTime < mSystemClock.elapsedRealtime();
- }
-
- @Override
- public int compareTo(@NonNull AlertEntry alertEntry) {
- return (mPostTime < alertEntry.mPostTime)
- ? 1 : ((mPostTime == alertEntry.mPostTime)
- ? mEntry.getKey().compareTo(alertEntry.mEntry.getKey()) : -1);
- }
-
- public void reset() {
- removeAutoRemovalCallbacks("reset()");
- mEntry = null;
- mRemoveAlertRunnable = null;
- }
-
- /**
- * Clear any pending removal runnables.
- */
- public void removeAutoRemovalCallbacks(@Nullable String reason) {
- final boolean removed = removeAutoRemovalCallbackInternal();
-
- if (removed) {
- mLogger.logAutoRemoveCanceled(mEntry, reason);
- }
- }
-
- private void scheduleAutoRemovalCallback(long delayMillis, @NonNull String reason) {
- if (mRemoveAlertRunnable == null) {
- Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
- return;
- }
-
- final boolean removed = removeAutoRemovalCallbackInternal();
-
- if (removed) {
- mLogger.logAutoRemoveRescheduled(mEntry, delayMillis, reason);
- } else {
- mLogger.logAutoRemoveScheduled(mEntry, delayMillis, reason);
- }
-
-
- mCancelRemoveAlertRunnable = mExecutor.executeDelayed(mRemoveAlertRunnable,
- delayMillis);
- }
-
- private boolean removeAutoRemovalCallbackInternal() {
- final boolean scheduled = (mCancelRemoveAlertRunnable != null);
-
- if (scheduled) {
- mCancelRemoveAlertRunnable.run();
- mCancelRemoveAlertRunnable = null;
- }
-
- return scheduled;
- }
-
- /**
- * Remove the alert at the earliest allowed removal time.
- */
- public void removeAsSoonAsPossible() {
- if (mRemoveAlertRunnable != null) {
- final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
- scheduleAutoRemovalCallback(timeLeft, "removeAsSoonAsPossible");
- }
- }
-
- /**
- * Calculate what the post time of a notification is at some current time.
- * @return the post time
- */
- protected long calculatePostTime() {
- return mSystemClock.elapsedRealtime();
- }
-
- /**
- * @return When the notification should auto-dismiss itself, based on
- * {@link SystemClock#elapsedRealtime()}
- */
- protected long calculateFinishTime() {
- // Overridden by HeadsUpManager HeadsUpEntry #calculateFinishTime
- return 0;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 7a2e82f24f46..bb6ee24b0ffe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -645,6 +645,10 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
+ "slot='" + mSlot + "' alpha=" + getAlpha() + " icon=" + mIcon
+ " visibleState=" + getVisibleStateString(getVisibleState())
+ " iconColor=#" + Integer.toHexString(mIconColor)
+ + " staticDrawableColor=#" + Integer.toHexString(mDrawableColor)
+ + " decorColor=#" + Integer.toHexString(mDecorColor)
+ + " animationStartColor=#" + Integer.toHexString(mAnimationStartColor)
+ + " currentSetColor=#" + Integer.toHexString(mCurrentSetColor)
+ " notification=" + mNotification + ')';
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
index 96279e2d2e44..5983fc17d4c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -157,9 +157,9 @@ class NotificationLaunchAnimatorController(
val summaryEntry = notificationEntry.parent?.summary
return when {
- headsUpManager.isAlerting(notificationKey) -> notification
+ headsUpManager.isHeadsUpEntry(notificationKey) -> notification
summaryEntry == null -> null
- headsUpManager.isAlerting(summaryEntry.key) -> summaryEntry.row
+ headsUpManager.isHeadsUpEntry(summaryEntry.key) -> summaryEntry.row
else -> null
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index cfe9fbe3af29..cdacb10e1676 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -171,7 +171,7 @@ public final class NotificationEntry extends ListEntry {
private int mBucket = BUCKET_ALERTING;
@Nullable private Long mPendingAnimationDuration;
private boolean mIsMarkedForUserTriggeredMovement;
- private boolean mIsAlerting;
+ private boolean mIsHeadsUpEntry;
public boolean mRemoteEditImeAnimatingAway;
public boolean mRemoteEditImeVisible;
@@ -965,12 +965,12 @@ public final class NotificationEntry extends ListEntry {
mIsMarkedForUserTriggeredMovement = marked;
}
- public void setIsAlerting(boolean isAlerting) {
- mIsAlerting = isAlerting;
+ public void setIsHeadsUpEntry(boolean isHeadsUpEntry) {
+ mIsHeadsUpEntry = isHeadsUpEntry;
}
- public boolean isAlerting() {
- return mIsAlerting;
+ public boolean isHeadsUpEntry() {
+ return mIsHeadsUpEntry;
}
/** Set whether this notification is currently used to animate a launch. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 314566eaf8d0..46806e66cb8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -221,7 +221,7 @@ class HeadsUpCoordinator @Inject constructor(
wasUpdated = false,
shouldHeadsUpEver = false,
shouldHeadsUpAgain = false,
- isAlerting = mHeadsUpManager.isAlerting(logicalSummary.key),
+ isAlerting = mHeadsUpManager.isHeadsUpEntry(logicalSummary.key),
isBinding = isEntryBinding(logicalSummary),
)
// If we transfer the alert and the summary isn't even attached, that means we
@@ -268,7 +268,7 @@ class HeadsUpCoordinator @Inject constructor(
wasUpdated = false,
shouldHeadsUpEver = true,
shouldHeadsUpAgain = true,
- isAlerting = mHeadsUpManager.isAlerting(childToReceiveParentAlert.key),
+ isAlerting = mHeadsUpManager.isHeadsUpEntry(childToReceiveParentAlert.key),
isBinding = isEntryBinding(childToReceiveParentAlert),
)
handlePostedEntry(
@@ -425,7 +425,7 @@ class HeadsUpCoordinator @Inject constructor(
val shouldHeadsUpEver =
mVisualInterruptionDecisionProvider.makeAndLogHeadsUpDecision(entry).shouldInterrupt
val shouldHeadsUpAgain = shouldHunAgain(entry)
- val isAlerting = mHeadsUpManager.isAlerting(entry.key)
+ val isAlerting = mHeadsUpManager.isHeadsUpEntry(entry.key)
val isBinding = isEntryBinding(entry)
val posted = mPostedEntries.compute(entry.key) { _, value ->
value?.also { update ->
@@ -469,7 +469,7 @@ class HeadsUpCoordinator @Inject constructor(
mEntriesUpdateTimes.remove(entry.key)
cancelHeadsUpBind(entry)
val entryKey = entry.key
- if (mHeadsUpManager.isAlerting(entryKey)) {
+ if (mHeadsUpManager.isHeadsUpEntry(entryKey)) {
// TODO: This should probably know the RemoteInputCoordinator's conditions,
// or otherwise reference that coordinator's state, rather than replicate its logic
val removeImmediatelyForRemoteInput = (mRemoteInputManager.isSpinning(entryKey) &&
@@ -719,7 +719,7 @@ class HeadsUpCoordinator @Inject constructor(
* Whether the notification is already alerting or binding so that it can imminently alert
*/
private fun isAttemptingToShowHun(entry: ListEntry) =
- mHeadsUpManager.isAlerting(entry.key) || isEntryBinding(entry)
+ mHeadsUpManager.isHeadsUpEntry(entry.key) || isEntryBinding(entry)
/**
* Whether the notification is already alerting/binding per [isAttemptingToShowHun] OR if it
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 a0129ff5cd90..8189fe03b2ed 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
@@ -134,7 +134,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable {
private final NotifStabilityManager mNotifStabilityManager =
new NotifStabilityManager("VisualStabilityCoordinator") {
private boolean canMoveForHeadsUp(NotificationEntry entry) {
- return entry != null && mHeadsUpManager.isAlerting(entry.getKey())
+ return entry != null && mHeadsUpManager.isHeadsUpEntry(entry.getKey())
&& !mVisibilityLocationProvider.isInVisibleLocation(entry);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
index e71d80c130da..5743ab0eae27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
@@ -68,7 +68,7 @@ public class OnUserInteractionCallbackImpl implements OnUserInteractionCallback
@NonNull
private DismissedByUserStats getDismissedByUserStats(NotificationEntry entry) {
int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
- if (mHeadsUpManager.isAlerting(entry.getKey())) {
+ if (mHeadsUpManager.isHeadsUpEntry(entry.getKey())) {
dismissalSurface = NotificationStats.DISMISSAL_PEEK;
} else if (mStatusBarStateController.isDozing()) {
dismissalSurface = NotificationStats.DISMISSAL_AOD;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
index 5180bab60fdf..b22e9fd2fb17 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt
@@ -68,7 +68,8 @@ constructor(
/**
* The same as [allNotificationsCount], but without flows, for easy access in synchronous code.
*/
- val allNotificationsCountValue: Int = repository.activeNotifications.value.individuals.size
+ val allNotificationsCountValue: Int
+ get() = repository.activeNotifications.value.individuals.size
/** Are any notifications being actively presented in the notification stack? */
val areAnyNotificationsPresent: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index ac499601b962..1677418c5c30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.init
import android.service.notification.StatusBarNotification
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
import com.android.systemui.people.widget.PeopleSpaceWidgetManager
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
import com.android.systemui.statusbar.NotificationListener
@@ -72,7 +71,6 @@ constructor(
private val animatedImageNotificationManager: AnimatedImageNotificationManager,
private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
private val bubblesOptional: Optional<Bubbles>,
- private val featureFlags: FeatureFlags
) : NotificationsController {
override fun initialize(
@@ -136,5 +134,8 @@ constructor(
}
}
- override fun getActiveNotificationsCount(): Int = notifLiveDataStore.activeNotifCount.value
+ override fun getActiveNotificationsCount(): Int {
+ NotificationsLiveDataStoreRefactor.assertInLegacyMode()
+ return notifLiveDataStore.activeNotifCount.value
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncGroupHeaderViewInflation.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncGroupHeaderViewInflation.kt
new file mode 100644
index 000000000000..44fc77f8bd55
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/AsyncGroupHeaderViewInflation.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.row.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the Async Group Header Inflation flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object AsyncGroupHeaderViewInflation {
+ /** The aconfig flag name */
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_ASYNC_GROUP_HEADER_INFLATION
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Is the async inflation of group header views enabled */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationAsyncGroupHeaderInflation()
+
+ /**
+ * 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 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/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index d9c51089d5f8..20fae88b6f33 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
@@ -26,9 +26,9 @@ import android.util.MathUtils;
import androidx.annotation.VisibleForTesting;
import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.res.R;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarState;
@@ -578,7 +578,7 @@ public class AmbientState implements Dumpable {
}
public boolean isPulsing(NotificationEntry entry) {
- return mPulsing && entry.isAlerting();
+ return mPulsing && entry.isHeadsUpEntry();
}
public void setPulsingRow(ExpandableNotificationRow row) {
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 e791a6490e15..04db653282ac 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
@@ -94,6 +94,7 @@ import com.android.systemui.flags.RefactorFlag;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.TouchLogger;
import com.android.systemui.statusbar.EmptyShadeView;
import com.android.systemui.statusbar.NotificationShelf;
@@ -187,6 +188,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private int mOverflingDistance;
private float mMaxOverScroll;
private boolean mIsBeingDragged;
+ private boolean mSendingTouchesToSceneFramework;
private int mLastMotionY;
private int mDownX;
private int mActivePointerId = INVALID_POINTER;
@@ -1509,7 +1511,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
*/
public void setExpandedHeight(float height) {
final boolean skipHeightUpdate = shouldSkipHeightUpdate();
- updateStackPosition();
+
+ // when scene framework is enabled, updateStackPosition is already called by
+ // updateTopPadding every time the stack moves, so skip it here to avoid flickering.
+ if (!SceneContainerFlag.isEnabled()) {
+ updateStackPosition();
+ }
if (!skipHeightUpdate) {
mExpandedHeight = height;
@@ -2450,6 +2457,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
/* notificationStackScrollLayout= */ this, mMaxDisplayedNotifications,
shelfIntrinsicHeight);
mIntrinsicContentHeight = height;
+ mController.setIntrinsicContentHeight(mIntrinsicContentHeight);
// The topPadding can be bigger than the regular padding when qs is expanded, in that
// state the maxPanelHeight and the contentHeight should be bigger
@@ -3558,8 +3566,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@Override
public boolean onTouchEvent(MotionEvent ev) {
- if (mTouchHandler != null && mTouchHandler.onTouchEvent(ev)) {
- return true;
+ if (mTouchHandler != null) {
+ boolean touchHandled = mTouchHandler.onTouchEvent(ev);
+ if (SceneContainerFlag.isEnabled() || touchHandled) {
+ return touchHandled;
+ }
}
return super.onTouchEvent(ev);
@@ -3567,6 +3578,27 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (SceneContainerFlag.isEnabled() && mIsBeingDragged) {
+ if (!mSendingTouchesToSceneFramework) {
+ // if this is the first touch being sent to the scene framework,
+ // convert it into a synthetic DOWN event.
+ mSendingTouchesToSceneFramework = true;
+ MotionEvent downEvent = MotionEvent.obtain(ev);
+ downEvent.setAction(MotionEvent.ACTION_DOWN);
+ mController.sendTouchToSceneFramework(downEvent);
+ downEvent.recycle();
+ } else {
+ mController.sendTouchToSceneFramework(ev);
+ }
+
+ if (
+ ev.getActionMasked() == MotionEvent.ACTION_UP
+ || ev.getActionMasked() == MotionEvent.ACTION_CANCEL
+ ) {
+ setIsBeingDragged(false);
+ }
+ return false;
+ }
return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
}
@@ -3633,6 +3665,18 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return true;
}
+ // If the scene framework is enabled, ignore all non-move gestures if we are currently
+ // dragging - they should be dispatched to the scene framework. Move gestures should be let
+ // through to determine if we are still dragging or not.
+ if (
+ SceneContainerFlag.isEnabled()
+ && mIsBeingDragged
+ && action != MotionEvent.ACTION_MOVE
+ ) {
+ setIsBeingDragged(false);
+ return false;
+ }
+
switch (action) {
case MotionEvent.ACTION_DOWN: {
if (getChildCount() == 0 || !isInContentBounds(ev)) {
@@ -3676,6 +3720,11 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
}
if (mIsBeingDragged) {
+ // Defer actual scrolling to the scene framework if enabled
+ if (SceneContainerFlag.isEnabled()) {
+ setIsBeingDragged(false);
+ return false;
+ }
// Scroll to follow the motion event
mLastMotionY = y;
float scrollAmount;
@@ -3770,9 +3819,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
protected boolean isInsideQsHeader(MotionEvent ev) {
- if (mQsHeader == null) {
- Log.wtf(TAG, "qsHeader is null while NSSL is handling a touch");
- return false;
+ if (SceneContainerFlag.isEnabled()) {
+ return ev.getY() < mController.getPlaceholderTop();
}
mQsHeader.getBoundsOnScreen(mQsHeaderBound);
@@ -4026,9 +4074,16 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
requestDisallowInterceptTouchEvent(true);
cancelLongPress();
resetExposedMenuView(true /* animate */, true /* force */);
+ } else {
+ mSendingTouchesToSceneFramework = false;
}
}
+ @VisibleForTesting
+ boolean getIsBeingDragged() {
+ return mIsBeingDragged;
+ }
+
public void requestDisallowLongPress() {
cancelLongPress();
}
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 c0e0b73c750d..6a66bb74f16d 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
@@ -80,6 +80,9 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEv
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.statusbar.CommandQueue;
@@ -120,6 +123,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.row.NotificationSnooze;
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
@@ -145,6 +149,7 @@ import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Named;
+import javax.inject.Provider;
/**
* Controller for {@link NotificationStackScrollLayout}.
@@ -181,6 +186,8 @@ public class NotificationStackScrollLayoutController implements Dumpable {
private final NotificationRemoteInputManager mRemoteInputManager;
private final VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
private final ShadeController mShadeController;
+ private final Provider<WindowRootView> mWindowRootView;
+ private final NotificationStackAppearanceInteractor mStackAppearanceInteractor;
private final KeyguardMediaController mKeyguardMediaController;
private final SysuiStatusBarStateController mStatusBarStateController;
private final KeyguardBypassController mKeyguardBypassController;
@@ -689,6 +696,9 @@ public class NotificationStackScrollLayoutController implements Dumpable {
SeenNotificationsInteractor seenNotificationsInteractor,
NotificationListViewBinder viewBinder,
ShadeController shadeController,
+ SceneContainerFlags sceneContainerFlags,
+ Provider<WindowRootView> windowRootView,
+ NotificationStackAppearanceInteractor stackAppearanceInteractor,
InteractionJankMonitor jankMonitor,
StackStateLogger stackLogger,
NotificationStackScrollLogger logger,
@@ -739,6 +749,8 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mVisibilityLocationProviderDelegator = visibilityLocationProviderDelegator;
mSeenNotificationsInteractor = seenNotificationsInteractor;
mShadeController = shadeController;
+ mWindowRootView = windowRootView;
+ mStackAppearanceInteractor = stackAppearanceInteractor;
mFeatureFlags = featureFlags;
mNotificationTargetsHelper = notificationTargetsHelper;
mSecureSettings = secureSettings;
@@ -1076,6 +1088,28 @@ public class NotificationStackScrollLayoutController implements Dumpable {
return mView.getIntrinsicContentHeight();
}
+ /**
+ * Dispatch a touch to the scene container framework.
+ * TODO(b/316965302): Replace findViewById to avoid DFS
+ */
+ public void sendTouchToSceneFramework(MotionEvent ev) {
+ View sceneContainer = mWindowRootView.get()
+ .findViewById(R.id.scene_container_root_composable);
+ if (sceneContainer != null) {
+ sceneContainer.dispatchTouchEvent(ev);
+ }
+ }
+
+ /** Get the y-coordinate of the top bound of the stack. */
+ public float getPlaceholderTop() {
+ return mStackAppearanceInteractor.getStackBounds().getValue().getTop();
+ }
+
+ /** Set the intrinsic height of the stack content without additional padding. */
+ public void setIntrinsicContentHeight(float intrinsicContentHeight) {
+ mStackAppearanceInteractor.setIntrinsicContentHeight(intrinsicContentHeight);
+ }
+
public void setIntrinsicPadding(int intrinsicPadding) {
mView.setIntrinsicPadding(intrinsicPadding);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
index e78a694735e0..aac3c28a3426 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt
@@ -30,4 +30,18 @@ class NotificationStackAppearanceRepository @Inject constructor() {
/** The corner radius of the notification stack, in dp. */
val cornerRadiusDp = MutableStateFlow(32f)
+
+ /**
+ * The height in px of the contents of notification stack. Depending on the number of
+ * notifications, this can exceed the space available on screen to show notifications, at which
+ * point the notification stack should become scrollable.
+ */
+ val intrinsicContentHeight = MutableStateFlow(0f)
+
+ /**
+ * The y-coordinate in px of top of the contents of the notification stack. This value can be
+ * negative, if the stack is scrolled such that its top extends beyond the top edge of the
+ * screen.
+ */
+ val contentTop = MutableStateFlow(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index 61a4dfcbd201..1dfde09f3a85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -34,12 +34,32 @@ constructor(
/** The bounds of the notification stack in the current scene. */
val stackBounds: StateFlow<NotificationContainerBounds> = repository.stackBounds.asStateFlow()
+ /** The corner radius of the notification stack, in dp. */
+ val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow()
+
+ /**
+ * The height in px of the contents of notification stack. Depending on the number of
+ * notifications, this can exceed the space available on screen to show notifications, at which
+ * point the notification stack should become scrollable.
+ */
+ val intrinsicContentHeight = repository.intrinsicContentHeight.asStateFlow()
+
+ /** The y-coordinate in px of top of the contents of the notification stack. */
+ val contentTop = repository.contentTop.asStateFlow()
+
/** Sets the position of the notification stack in the current scene. */
fun setStackBounds(bounds: NotificationContainerBounds) {
check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" }
repository.stackBounds.value = bounds
}
- /** The corner radius of the notification stack, in dp. */
- val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow()
+ /** Sets the height of the contents of the notification stack. */
+ fun setIntrinsicContentHeight(height: Float) {
+ repository.intrinsicContentHeight.value = height
+ }
+
+ /** Sets the y-coord in px of the top of the contents of the notification stack. */
+ fun setContentTop(startY: Float) {
+ repository.contentTop.value = startY
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
index a9b542dcce2d..ed15f557fb39 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt
@@ -25,6 +25,7 @@ import com.android.systemui.statusbar.notification.stack.AmbientState
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel
+import kotlin.math.pow
import kotlin.math.roundToInt
import kotlinx.coroutines.launch
@@ -43,24 +44,28 @@ object NotificationStackAppearanceViewBinder {
repeatOnLifecycle(Lifecycle.State.CREATED) {
launch {
viewModel.stackBounds.collect { bounds ->
- controller.updateTopPadding(
- bounds.top,
- controller.isAddOrRemoveAnimationPending
- )
controller.setRoundedClippingBounds(
- it.left,
- it.top,
- it.right,
- it.bottom,
+ bounds.left.roundToInt(),
+ bounds.top.roundToInt(),
+ bounds.right.roundToInt(),
+ bounds.bottom.roundToInt(),
viewModel.cornerRadiusDp.value.dpToPx(context),
viewModel.cornerRadiusDp.value.dpToPx(context),
)
}
}
+
+ launch {
+ viewModel.contentTop.collect {
+ controller.updateTopPadding(it, controller.isAddOrRemoveAnimationPending)
+ }
+ }
+
launch {
viewModel.expandFraction.collect { expandFraction ->
ambientState.expansionFraction = expandFraction
controller.expandedHeight = expandFraction * controller.view.height
+ controller.setMaxAlphaForExpansion(expandFraction.pow(0.75f))
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
index 834d3ffe63c9..74db5831f7f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt
@@ -41,4 +41,7 @@ constructor(
/** The corner radius of the notification stack, in dp. */
val cornerRadiusDp: StateFlow<Float> = stackAppearanceInteractor.cornerRadiusDp
+
+ /** The y-coordinate in px of top of the contents of the notification stack. */
+ val contentTop: StateFlow<Float> = stackAppearanceInteractor.contentTop
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 9f22118e3332..385f0619288d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -21,9 +21,11 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled
import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
/**
@@ -35,6 +37,7 @@ class NotificationsPlaceholderViewModel
@Inject
constructor(
private val interactor: NotificationStackAppearanceInteractor,
+ shadeInteractor: ShadeInteractor,
flags: SceneContainerFlags,
featureFlags: FeatureFlagsClassic,
) {
@@ -66,4 +69,22 @@ constructor(
/** The corner radius of the placeholder, in dp. */
val cornerRadiusDp: StateFlow<Float> = interactor.cornerRadiusDp
+
+ /**
+ * The height in px of the contents of notification stack. Depending on the number of
+ * notifications, this can exceed the space available on screen to show notifications, at which
+ * point the notification stack should become scrollable.
+ */
+ val intrinsicContentHeight = interactor.intrinsicContentHeight
+
+ /**
+ * The amount [0-1] that the shade has been opened. At 0, the shade is closed; at 1, the shade
+ * is open.
+ */
+ val expandFraction: Flow<Float> = shadeInteractor.shadeExpansion
+
+ /** Sets the y-coord in px of the top of the contents of the notification stack. */
+ fun onContentTopChanged(padding: Float) {
+ interactor.setContentTop(padding)
+ }
}
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 5ee38bebc99b..a48fb45861d2 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
@@ -240,13 +240,13 @@ constructor(
*/
val translationY: Flow<Float> =
combine(
- isOnLockscreen,
+ isOnLockscreenWithoutShade,
merge(
keyguardInteractor.keyguardTranslationY,
occludedToLockscreenTransitionViewModel.lockscreenTranslationY,
)
- ) { isOnLockscreen, translationY ->
- if (isOnLockscreen) {
+ ) { isOnLockscreenWithoutShade, translationY ->
+ if (isOnLockscreenWithoutShade) {
translationY
} else {
0f
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index e66d9e8ca531..b07ba3c5b326 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -222,9 +222,9 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp
mReleaseOnExpandFinish = false;
} else {
for (NotificationEntry entry : mEntriesToRemoveAfterExpand) {
- if (isAlerting(entry.getKey())) {
+ if (isHeadsUpEntry(entry.getKey())) {
// Maybe the heads-up was removed already
- removeAlertEntry(entry.getKey());
+ removeEntry(entry.getKey());
}
}
}
@@ -357,9 +357,9 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp
private final OnReorderingAllowedListener mOnReorderingAllowedListener = () -> {
mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
for (NotificationEntry entry : mEntriesToRemoveWhenReorderingAllowed) {
- if (isAlerting(entry.getKey())) {
+ if (isHeadsUpEntry(entry.getKey())) {
// Maybe the heads-up was removed already
- removeAlertEntry(entry.getKey());
+ removeEntry(entry.getKey());
}
}
mEntriesToRemoveWhenReorderingAllowed.clear();
@@ -370,14 +370,14 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp
// HeadsUpManager utility (protected) methods overrides:
@Override
- protected HeadsUpEntry createAlertEntry() {
+ protected HeadsUpEntry createHeadsUpEntry() {
return mEntryPool.acquire();
}
@Override
- protected void onAlertEntryRemoved(AlertEntry alertEntry) {
- super.onAlertEntryRemoved(alertEntry);
- mEntryPool.release((HeadsUpEntryPhone) alertEntry);
+ protected void onEntryRemoved(HeadsUpEntry headsUpEntry) {
+ super.onEntryRemoved(headsUpEntry);
+ mEntryPool.release((HeadsUpEntryPhone) headsUpEntry);
}
@Override
@@ -403,7 +403,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp
@Nullable
private HeadsUpEntryPhone getHeadsUpEntryPhone(@NonNull String key) {
- return (HeadsUpEntryPhone) mAlertEntries.get(key);
+ return (HeadsUpEntryPhone) mHeadsUpEntryMap.get(key);
}
@Nullable
@@ -455,7 +455,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp
} else if (mTrackingHeadsUp) {
mEntriesToRemoveAfterExpand.add(entry);
} else {
- removeAlertEntry(entry.getKey());
+ removeEntry(entry.getKey());
}
};
@@ -529,13 +529,13 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp
mStatusBarState = newState;
if (wasKeyguard && !isKeyguard && mBypassController.getBypassEnabled()) {
ArrayList<String> keysToRemove = new ArrayList<>();
- for (AlertEntry entry : mAlertEntries.values()) {
+ for (HeadsUpEntry entry : mHeadsUpEntryMap.values()) {
if (entry.mEntry != null && entry.mEntry.isBubble() && !entry.isSticky()) {
keysToRemove.add(entry.mEntry.getKey());
}
}
for (String key : keysToRemove) {
- removeAlertEntry(key);
+ removeEntry(key);
}
}
}
@@ -545,7 +545,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp
if (!isDozing) {
// Let's make sure all huns we got while dozing time out within the normal timeout
// duration. Otherwise they could get stuck for a very long time
- for (AlertEntry entry : mAlertEntries.values()) {
+ for (HeadsUpEntry entry : mHeadsUpEntryMap.values()) {
entry.updateEntry(true /* updatePostTime */, "onDozingChanged(false)");
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index f34a44a5c4b0..be5c6b3ba069 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -918,8 +918,15 @@ public class NotificationIconContainer extends ViewGroup {
}
}
icon.setVisibleState(visibleState, animationsAllowed);
- icon.setIconColor(mOverrideIconColor ? mThemedTextColorPrimary : iconColor,
- needsCannedAnimation && animationsAllowed);
+ if (NotificationIconContainerRefactor.isEnabled()) {
+ if (mOverrideIconColor) {
+ icon.setIconColor(mThemedTextColorPrimary,
+ /* animate= */ needsCannedAnimation && animationsAllowed);
+ }
+ } else {
+ icon.setIconColor(mOverrideIconColor ? mThemedTextColorPrimary : iconColor,
+ needsCannedAnimation && animationsAllowed);
+ }
if (animate) {
animateTo(icon, animationProperties);
} else {
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 9da61112fd0c..4ee061d05a3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -562,7 +562,7 @@ public class StatusBarNotificationActivityStarter implements NotificationActivit
private void removeHunAfterClick(ExpandableNotificationRow row) {
String key = row.getEntry().getSbn().getKey();
- if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(key)) {
+ if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUpEntry(key)) {
// Release the HUN notification to the shade.
if (mPresenter.isPresenterFullyCollapsed()) {
HeadsUpUtil.setNeedsHeadsUpDisappearAnimationAfterClick(row, true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
index 2b90e649a154..e309c32df64e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -265,13 +265,6 @@ abstract class StatusBarPipelineModule {
return factory.create("VerboseMobileViewLog", 100)
}
- @Provides
- @SysUISingleton
- @OemSatelliteInputLog
- fun provideOemSatelliteInputLog(factory: LogBufferFactory): LogBuffer {
- return factory.create("DeviceBasedSatelliteInputLog", 32)
- }
-
const val FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON =
"FirstMobileSubShowingNetworkTypeIcon"
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt
index 8400fb08e147..e3c3139f6906 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/icons/shared/BindableIconsRegistry.kt
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.pipeline.icons.shared
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.pipeline.icons.shared.model.BindableIcon
-import com.android.systemui.statusbar.pipeline.satellite.ui.DeviceBasedSatelliteBindableIcon
import javax.inject.Inject
/**
@@ -39,12 +38,11 @@ interface BindableIconsRegistry {
class BindableIconsRegistryImpl
@Inject
constructor(
- /** Bindables go here */
- oemSatellite: DeviceBasedSatelliteBindableIcon
+/** Bindables go here */
) : BindableIconsRegistry {
/**
* Adding the injected bindables to this list will get them registered with
* StatusBarIconController
*/
- override val bindableIcons: List<BindableIcon> = listOf(oemSatellite)
+ override val bindableIcons: List<BindableIcon> = listOf()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index 5e6e36dd69a9..de46a5ed99d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -19,17 +19,11 @@ package com.android.systemui.statusbar.pipeline.satellite.data.prod
import android.os.OutcomeReceiver
import android.telephony.satellite.NtnSignalStrengthCallback
import android.telephony.satellite.SatelliteManager
-import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS
import android.telephony.satellite.SatelliteModemStateCallback
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.LogLevel
-import com.android.systemui.log.core.MessageInitializer
-import com.android.systemui.log.core.MessagePrinter
-import com.android.systemui.statusbar.pipeline.dagger.OemSatelliteInputLog
import com.android.systemui.statusbar.pipeline.satellite.data.DeviceBasedSatelliteRepository
import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Companion.whenSupported
import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.NotSupported
@@ -129,7 +123,6 @@ constructor(
satelliteManagerOpt: Optional<SatelliteManager>,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
- @OemSatelliteInputLog private val logBuffer: LogBuffer,
private val systemClock: SystemClock,
) : DeviceBasedSatelliteRepository {
@@ -152,11 +145,6 @@ constructor(
ensureMinUptime(systemClock, MIN_UPTIME)
satelliteSupport.value = satelliteManager.checkSatelliteSupported()
- logBuffer.i(
- { str1 = satelliteSupport.value.toString() },
- { "Checked for system support. support=$str1" },
- )
-
// We only need to check location availability if this mode is supported
if (satelliteSupport.value is Supported) {
isSatelliteAllowedForCurrentLocation.subscriptionCount
@@ -171,9 +159,6 @@ constructor(
* connection might cause more frequent checks.
*/
while (true) {
- logBuffer.i {
- "requestIsSatelliteCommunicationAllowedForCurrentLocation"
- }
checkIsSatelliteAllowed()
delay(POLLING_INTERVAL_MS)
}
@@ -182,8 +167,6 @@ constructor(
}
}
} else {
- logBuffer.i { "Satellite manager is null" }
-
satelliteSupport.value = NotSupported
}
}
@@ -198,21 +181,12 @@ constructor(
private fun connectionStateFlow(sm: SupportedSatelliteManager): Flow<SatelliteConnectionState> =
conflatedCallbackFlow {
val cb = SatelliteModemStateCallback { state ->
- logBuffer.i({ int1 = state }) { "onSatelliteModemStateChanged: state=$int1" }
trySend(SatelliteConnectionState.fromModemState(state))
}
- var registered = false
-
- try {
- val res =
- sm.registerForSatelliteModemStateChanged(bgDispatcher.asExecutor(), cb)
- registered = res == SATELLITE_RESULT_SUCCESS
- } catch (e: Exception) {
- logBuffer.e("error registering for modem state", e)
- }
+ sm.registerForSatelliteModemStateChanged(bgDispatcher.asExecutor(), cb)
- awaitClose { if (registered) sm.unregisterForSatelliteModemStateChanged(cb) }
+ awaitClose { sm.unregisterForSatelliteModemStateChanged(cb) }
}
.flowOn(bgDispatcher)
@@ -223,21 +197,12 @@ constructor(
private fun signalStrengthFlow(sm: SupportedSatelliteManager) =
conflatedCallbackFlow {
val cb = NtnSignalStrengthCallback { signalStrength ->
- logBuffer.i({ int1 = signalStrength.level }) {
- "onNtnSignalStrengthChanged: level=$int1"
- }
trySend(signalStrength.level)
}
- var registered = false
- try {
- sm.registerForNtnSignalStrengthChanged(bgDispatcher.asExecutor(), cb)
- registered = true
- } catch (e: Exception) {
- logBuffer.e("error registering for signal strength", e)
- }
+ sm.registerForNtnSignalStrengthChanged(bgDispatcher.asExecutor(), cb)
- awaitClose { if (registered) sm.unregisterForNtnSignalStrengthChanged(cb) }
+ awaitClose { sm.unregisterForNtnSignalStrengthChanged(cb) }
}
.flowOn(bgDispatcher)
@@ -248,15 +213,11 @@ constructor(
bgDispatcher.asExecutor(),
object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> {
override fun onError(e: SatelliteManager.SatelliteException) {
- logBuffer.e(
- "Found exception when checking availability",
- e,
- )
+ android.util.Log.e(TAG, "Found exception when checking for satellite: ", e)
isSatelliteAllowedForCurrentLocation.value = false
}
override fun onResult(allowed: Boolean) {
- logBuffer.i { allowed.toString() }
isSatelliteAllowedForCurrentLocation.value = allowed
}
}
@@ -278,12 +239,6 @@ constructor(
}
override fun onError(error: SatelliteManager.SatelliteException) {
- logBuffer.e(
- "Exception when checking for satellite support. " +
- "Assuming it is not supported for this device.",
- error,
- )
-
// Assume that an error means it's not supported
continuation.resume(NotSupported)
}
@@ -309,19 +264,5 @@ constructor(
delay(timeTilMinUptime)
}
}
-
- /** A couple of convenience logging methods rather than a whole class */
- private fun LogBuffer.i(
- initializer: MessageInitializer = {},
- printer: MessagePrinter,
- ) = this.log(TAG, LogLevel.INFO, initializer, printer)
-
- private fun LogBuffer.e(message: String, exception: Throwable? = null) =
- this.log(
- tag = TAG,
- level = LogLevel.ERROR,
- message = message,
- exception = exception,
- )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/DeviceBasedSatelliteBindableIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/DeviceBasedSatelliteBindableIcon.kt
deleted file mode 100644
index f5d0f6b8f07c..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/DeviceBasedSatelliteBindableIcon.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.satellite.ui
-
-import android.content.Context
-import com.android.internal.telephony.flags.Flags.oemEnabledSatelliteFlag
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.pipeline.icons.shared.model.BindableIcon
-import com.android.systemui.statusbar.pipeline.icons.shared.model.ModernStatusBarViewCreator
-import com.android.systemui.statusbar.pipeline.satellite.ui.binder.DeviceBasedSatelliteIconBinder
-import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModel
-import com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView
-import javax.inject.Inject
-
-@SysUISingleton
-class DeviceBasedSatelliteBindableIcon
-@Inject
-constructor(
- context: Context,
- viewModel: DeviceBasedSatelliteViewModel,
-) : BindableIcon {
- override val slot: String =
- context.getString(com.android.internal.R.string.status_bar_oem_satellite)
-
- override val initializer = ModernStatusBarViewCreator { context ->
- SingleBindableStatusBarIconView.createView(context).also { view ->
- view.initView(slot) { DeviceBasedSatelliteIconBinder.bind(view, viewModel) }
- }
- }
-
- override val shouldBindIcon: Boolean = oemEnabledSatelliteFlag()
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/binder/DeviceBasedSatelliteIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/binder/DeviceBasedSatelliteIconBinder.kt
deleted file mode 100644
index 59ac5f29b66c..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/binder/DeviceBasedSatelliteIconBinder.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.satellite.ui.binder
-
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.common.ui.binder.IconViewBinder
-import com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel.DeviceBasedSatelliteViewModel
-import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
-import com.android.systemui.statusbar.pipeline.shared.ui.view.SingleBindableStatusBarIconView
-import kotlinx.coroutines.launch
-
-object DeviceBasedSatelliteIconBinder {
- fun bind(
- view: SingleBindableStatusBarIconView,
- viewModel: DeviceBasedSatelliteViewModel,
- ): ModernStatusBarViewBinding {
- return SingleBindableStatusBarIconView.withDefaultBinding(
- view = view,
- shouldBeVisible = { viewModel.icon.value != null }
- ) {
- lifecycleScope.launch {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.icon.collect { newIcon ->
- if (newIcon == null) {
- view.iconView.setImageDrawable(null)
- } else {
- IconViewBinder.bind(newIcon, view.iconView)
- }
- }
- }
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
deleted file mode 100644
index 6938d667ca81..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/model/SatelliteIconModel.kt
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.satellite.ui.model
-
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
-
-/**
- * Define the [Icon] that relates to a given satellite connection state + level. Note that for now
- * We don't need any data class box, so we can just use a simple mapping function.
- */
-object SatelliteIconModel {
- fun fromConnectionState(
- connectionState: SatelliteConnectionState,
- signalStrength: Int,
- ): Icon? =
- when (connectionState) {
- // TODO(b/316635648): check if this should be null
- SatelliteConnectionState.Unknown,
- SatelliteConnectionState.Off,
- SatelliteConnectionState.On ->
- Icon.Resource(
- res = R.drawable.ic_satellite_not_connected,
- contentDescription = null,
- )
- SatelliteConnectionState.Connected -> fromSignalStrength(signalStrength)
- }
-
- private fun fromSignalStrength(
- signalStrength: Int,
- ): Icon? =
- // TODO(b/316634365): these need content descriptions
- when (signalStrength) {
- // No signal
- 0 -> Icon.Resource(res = R.drawable.ic_satellite_connected_0, contentDescription = null)
-
- // Poor -> Moderate
- 1,
- 2 -> Icon.Resource(res = R.drawable.ic_satellite_connected_1, contentDescription = null)
-
- // Good -> Great
- 3,
- 4 -> Icon.Resource(res = R.drawable.ic_satellite_connected_2, contentDescription = null)
- else -> null
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
deleted file mode 100644
index 0051161eff35..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModel.kt
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel
-
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
-import com.android.systemui.statusbar.pipeline.satellite.ui.model.SatelliteIconModel
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.stateIn
-
-/**
- * View-Model for the device-based satellite icon. This icon will only show in the status bar if
- * satellite is available AND all other service states are considered OOS.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-class DeviceBasedSatelliteViewModel
-@Inject
-constructor(
- interactor: DeviceBasedSatelliteInteractor,
- @Application scope: CoroutineScope,
-) {
- private val shouldShowIcon: StateFlow<Boolean> =
- interactor.areAllConnectionsOutOfService
- .flatMapLatest { allOos ->
- if (!allOos) {
- flowOf(false)
- } else {
- interactor.isSatelliteAllowed
- }
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), false)
-
- val icon: StateFlow<Icon?> =
- combine(
- shouldShowIcon,
- interactor.connectionState,
- interactor.signalStrength,
- ) { shouldShow, state, signalStrength ->
- if (shouldShow) {
- SatelliteIconModel.fromConnectionState(state, signalStrength)
- } else {
- null
- }
- }
- .stateIn(scope, SharingStarted.WhileSubscribed(), null)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
index 25a2c9dd3caf..3b87bed2e0ef 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/ModernStatusBarView.kt
@@ -103,7 +103,7 @@ open class ModernStatusBarView(context: Context, attrs: AttributeSet?) :
*
* Creates a dot view, and uses [bindingCreator] to get and set the binding.
*/
- open fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) {
+ fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) {
// The dot view requires [slot] to be set, and the [binding] may require an instantiated dot
// view. So, this is the required order.
this.slot = slot
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconView.kt
deleted file mode 100644
index c663c37fec98..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconView.kt
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.shared.ui.view
-
-import android.content.Context
-import android.content.res.ColorStateList
-import android.graphics.Color
-import android.util.AttributeSet
-import android.view.LayoutInflater
-import android.view.View
-import android.widget.ImageView
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.res.R
-import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
-import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewBinding
-import com.android.systemui.statusbar.pipeline.shared.ui.binder.ModernStatusBarViewVisibilityHelper
-import kotlinx.coroutines.awaitCancellation
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.launch
-
-/** Simple single-icon view that is bound to bindable_status_bar_icon.xml */
-class SingleBindableStatusBarIconView(
- context: Context,
- attrs: AttributeSet?,
-) : ModernStatusBarView(context, attrs) {
-
- internal lateinit var iconView: ImageView
- internal lateinit var dotView: StatusBarIconView
-
- override fun toString(): String {
- return "SingleBindableStatusBarIcon(" +
- "slot='$slot', " +
- "isCollecting=${binding.isCollecting()}, " +
- "visibleState=${StatusBarIconView.getVisibleStateString(visibleState)}); " +
- "viewString=${super.toString()}"
- }
-
- override fun initView(slot: String, bindingCreator: () -> ModernStatusBarViewBinding) {
- super.initView(slot, bindingCreator)
-
- iconView = requireViewById(R.id.icon_view)
- dotView = requireViewById(R.id.status_bar_dot)
- }
-
- companion object {
- fun createView(
- context: Context,
- ): SingleBindableStatusBarIconView {
- return LayoutInflater.from(context).inflate(R.layout.bindable_status_bar_icon, null)
- as SingleBindableStatusBarIconView
- }
-
- /**
- * Using a given binding [block], create the necessary scaffolding to handle the general
- * case of a single status bar icon. This includes eliding into a dot view when there is not
- * enough space, and handling tint.
- *
- * [block] should be a simple [launch] call that handles updating the single icon view with
- * its new view. Currently there is no simple way to e.g., extend to handle multiple tints
- * for dual-layered icons, and any more complex logic should probably find a way to return
- * its own version of [ModernStatusBarViewBinding].
- */
- fun withDefaultBinding(
- view: SingleBindableStatusBarIconView,
- shouldBeVisible: () -> Boolean,
- block: suspend LifecycleOwner.(View) -> Unit
- ): SingleBindableStatusBarIconViewBinding {
- @StatusBarIconView.VisibleState
- val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN)
-
- val iconTint: MutableStateFlow<Int> = MutableStateFlow(Color.WHITE)
- val decorTint: MutableStateFlow<Int> = MutableStateFlow(Color.WHITE)
-
- var isCollecting: Boolean = false
-
- view.repeatWhenAttached {
- // Child binding
- block(view)
-
- lifecycleScope.launch {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- // isVisible controls the visibility state of the outer group, and thus it
- // needs
- // to run in the CREATED lifecycle so it can continue to watch while
- // invisible
- // See (b/291031862) for details
- launch {
- visibilityState.collect { visibilityState ->
- // for b/296864006, we can not hide all the child views if
- // visibilityState is STATE_HIDDEN. Because hiding all child views
- // would cause the
- // getWidth() of this view return 0, and that would cause the
- // translation
- // calculation fails in StatusIconContainer. Therefore, like class
- // MobileIconBinder, instead of set the child views visibility to
- // View.GONE,
- // we set their visibility to View.INVISIBLE to make them invisible
- // but
- // keep the width.
- ModernStatusBarViewVisibilityHelper.setVisibilityState(
- visibilityState,
- view.iconView,
- view.dotView,
- )
- }
- }
-
- launch {
- iconTint.collect { tint ->
- val tintList = ColorStateList.valueOf(tint)
- view.iconView.imageTintList = tintList
- view.dotView.setDecorColor(tint)
- }
- }
-
- launch {
- decorTint.collect { decorTint -> view.dotView.setDecorColor(decorTint) }
- }
-
- try {
- awaitCancellation()
- } finally {
- isCollecting = false
- }
- }
- }
- }
-
- return object : SingleBindableStatusBarIconViewBinding {
- override val decorTint: Int
- get() = decorTint.value
-
- override val iconTint: Int
- get() = iconTint.value
-
- override fun getShouldIconBeVisible(): Boolean {
- return shouldBeVisible()
- }
-
- override fun onVisibilityStateChanged(state: Int) {
- visibilityState.value = state
- }
-
- override fun onIconTintChanged(newTint: Int, contrastTint: Int) {
- iconTint.value = newTint
- }
-
- override fun onDecorTintChanged(newTint: Int) {
- decorTint.value = newTint
- }
-
- override fun isCollecting(): Boolean {
- return isCollecting
- }
- }
- }
- }
-}
-
-@VisibleForTesting
-interface SingleBindableStatusBarIconViewBinding : ModernStatusBarViewBinding {
- val iconTint: Int
- val decorTint: Int
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index e9711284cfca..1528c9befda7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -26,6 +26,9 @@ import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.Handler;
import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import com.android.internal.logging.MetricsLogger;
@@ -34,7 +37,6 @@ import com.android.internal.logging.UiEventLogger;
import com.android.systemui.EventLogTags;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.res.R;
-import com.android.systemui.statusbar.AlertingNotificationManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.util.ListenerSet;
@@ -43,14 +45,14 @@ import com.android.systemui.util.settings.GlobalSettings;
import com.android.systemui.util.time.SystemClock;
import java.io.PrintWriter;
+import java.util.stream.Stream;
/**
* A manager which handles heads up notifications which is a special mode where
* they simply peek from the top of the screen.
*/
-public abstract class BaseHeadsUpManager extends AlertingNotificationManager implements
- HeadsUpManager {
- private static final String TAG = "HeadsUpManager";
+public abstract class BaseHeadsUpManager implements HeadsUpManager {
+ private static final String TAG = "BaseHeadsUpManager";
private static final String SETTING_HEADS_UP_SNOOZE_LENGTH_MS = "heads_up_snooze_length_ms";
protected final ListenerSet<OnHeadsUpChangedListener> mListeners = new ListenerSet<>();
@@ -67,6 +69,14 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp
private final UiEventLogger mUiEventLogger;
+ protected final SystemClock mSystemClock;
+ protected final ArrayMap<String, HeadsUpEntry> mHeadsUpEntryMap = new ArrayMap<>();
+ protected final HeadsUpManagerLogger mLogger;
+ protected int mMinimumDisplayTime;
+ protected int mStickyForSomeTimeAutoDismissTime;
+ protected int mAutoDismissTime;
+ protected DelayableExecutor mExecutor;
+
/**
* Enum entry for notification peek logged from this class.
*/
@@ -91,7 +101,9 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp
@Main DelayableExecutor executor,
AccessibilityManagerWrapper accessibilityManagerWrapper,
UiEventLogger uiEventLogger) {
- super(logger, systemClock, executor);
+ mLogger = logger;
+ mExecutor = executor;
+ mSystemClock = systemClock;
mContext = context;
mAccessibilityMgr = accessibilityManagerWrapper;
mUiEventLogger = uiEventLogger;
@@ -138,20 +150,136 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp
mListeners.remove(listener);
}
- /** Updates the notification with the given key. */
- public void updateNotification(@NonNull String key, boolean alert) {
- super.updateNotification(key, alert);
- HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
- if (alert && headsUpEntry != null) {
- setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry));
+ /**
+ * Called when posting a new notification that should appear on screen.
+ * Adds the notification to be managed.
+ * @param entry entry to show
+ */
+ @Override
+ public void showNotification(@NonNull NotificationEntry entry) {
+ mLogger.logShowNotification(entry);
+ addEntry(entry);
+ updateNotification(entry.getKey(), true /* show */);
+ entry.setInterruption();
+ }
+
+ /**
+ * Try to remove the notification. May not succeed if the notification has not been shown long
+ * enough and needs to be kept around.
+ * @param key the key of the notification to remove
+ * @param releaseImmediately force a remove regardless of earliest removal time
+ * @return true if notification is removed, false otherwise
+ */
+ @Override
+ public boolean removeNotification(@NonNull String key, boolean releaseImmediately) {
+ mLogger.logRemoveNotification(key, releaseImmediately);
+ HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+ if (headsUpEntry == null) {
+ return true;
+ }
+ if (releaseImmediately || canRemoveImmediately(key)) {
+ removeEntry(key);
+ } else {
+ headsUpEntry.removeAsSoonAsPossible();
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Called when the notification state has been updated.
+ * @param key the key of the entry that was updated
+ * @param show whether the notification should show again and force reevaluation of
+ * removal time
+ */
+ public void updateNotification(@NonNull String key, boolean show) {
+ HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+ mLogger.logUpdateNotification(key, show, headsUpEntry != null);
+ if (headsUpEntry == null) {
+ // the entry was released before this update (i.e by a listener) This can happen
+ // with the groupmanager
+ return;
+ }
+
+ headsUpEntry.mEntry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+
+ if (show) {
+ headsUpEntry.updateEntry(true /* updatePostTime */, "updateNotification");
+ if (headsUpEntry != null) {
+ setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry));
+ }
+ }
+ }
+
+ /**
+ * Clears all managed notifications.
+ */
+ public void releaseAllImmediately() {
+ mLogger.logReleaseAllImmediately();
+ // A copy is necessary here as we are changing the underlying map. This would cause
+ // undefined behavior if we iterated over the key set directly.
+ ArraySet<String> keysToRemove = new ArraySet<>(mHeadsUpEntryMap.keySet());
+ for (String key : keysToRemove) {
+ removeEntry(key);
+ }
+ }
+
+ /**
+ * Returns the entry if it is managed by this manager.
+ * @param key key of notification
+ * @return the entry
+ */
+ @Nullable
+ public NotificationEntry getEntry(@NonNull String key) {
+ HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+ return headsUpEntry != null ? headsUpEntry.mEntry : null;
+ }
+
+ /**
+ * Returns the stream of all current notifications managed by this manager.
+ * @return all entries
+ */
+ @NonNull
+ @Override
+ public Stream<NotificationEntry> getAllEntries() {
+ return mHeadsUpEntryMap.values().stream().map(headsUpEntry -> headsUpEntry.mEntry);
+ }
+
+ /**
+ * Whether or not there are any active notifications.
+ * @return true if there is an entry, false otherwise
+ */
+ @Override
+ public boolean hasNotifications() {
+ return !mHeadsUpEntryMap.isEmpty();
+ }
+
+ /**
+ * @return true if the notification is managed by this manager
+ */
+ public boolean isHeadsUpEntry(@NonNull String key) {
+ return mHeadsUpEntryMap.containsKey(key);
+ }
+
+ /**
+ * @param key
+ * @return When a HUN entry should be removed in milliseconds from now
+ */
+ @Override
+ public long getEarliestRemovalTime(String key) {
+ HeadsUpEntry entry = mHeadsUpEntryMap.get(key);
+ if (entry != null) {
+ return Math.max(0, entry.mEarliestRemovalTime - mSystemClock.elapsedRealtime());
}
+ return 0;
}
protected boolean shouldHeadsUpBecomePinned(@NonNull NotificationEntry entry) {
final HeadsUpEntry headsUpEntry = getHeadsUpEntry(entry.getKey());
if (headsUpEntry == null) {
// This should not happen since shouldHeadsUpBecomePinned is always called after adding
- // the NotificationEntry into AlertingNotificationManager's mAlertEntries map.
+ // the NotificationEntry into mHeadsUpEntryMap.
return hasFullScreenIntent(entry);
}
return hasFullScreenIntent(entry) && !headsUpEntry.mWasUnpinned;
@@ -190,24 +318,65 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp
return FLAG_CONTENT_VIEW_HEADS_UP;
}
- @Override
- protected void onAlertEntryAdded(AlertEntry alertEntry) {
- NotificationEntry entry = alertEntry.mEntry;
+ /**
+ * Add a new entry and begin managing it.
+ * @param entry the entry to add
+ */
+ protected final void addEntry(@NonNull NotificationEntry entry) {
+ HeadsUpEntry headsUpEntry = createHeadsUpEntry();
+ headsUpEntry.setEntry(entry);
+ mHeadsUpEntryMap.put(entry.getKey(), headsUpEntry);
+ onEntryAdded(headsUpEntry);
+ entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ entry.setIsHeadsUpEntry(true);
+ }
+
+ /**
+ * Manager-specific logic that should occur when an entry is added.
+ * @param headsUpEntry entry added
+ */
+ protected void onEntryAdded(HeadsUpEntry headsUpEntry) {
+ NotificationEntry entry = headsUpEntry.mEntry;
entry.setHeadsUp(true);
final boolean shouldPin = shouldHeadsUpBecomePinned(entry);
- setEntryPinned((HeadsUpEntry) alertEntry, shouldPin);
+ setEntryPinned(headsUpEntry, shouldPin);
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 1 /* visible */);
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpStateChanged(entry, true);
}
}
- @Override
- protected void onAlertEntryRemoved(AlertEntry alertEntry) {
- NotificationEntry entry = alertEntry.mEntry;
+ /**
+ * Remove a notification and reset the entry.
+ * @param key key of notification to remove
+ */
+ protected final void removeEntry(@NonNull String key) {
+ HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+ if (headsUpEntry == null) {
+ return;
+ }
+ NotificationEntry entry = headsUpEntry.mEntry;
+
+ // If the notification is animating, we will remove it at the end of the animation.
+ if (entry != null && entry.isExpandAnimationRunning()) {
+ return;
+ }
+ entry.demoteStickyHun();
+ mHeadsUpEntryMap.remove(key);
+ onEntryRemoved(headsUpEntry);
+ entry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+ headsUpEntry.reset();
+ }
+
+ /**
+ * Manager-specific logic that should occur when an entry is removed.
+ * @param headsUpEntry entry removed
+ */
+ protected void onEntryRemoved(HeadsUpEntry headsUpEntry) {
+ NotificationEntry entry = headsUpEntry.mEntry;
entry.setHeadsUp(false);
- setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */);
+ setEntryPinned(headsUpEntry, false /* isPinned */);
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
mLogger.logNotificationActuallyRemoved(entry);
for (OnHeadsUpChangedListener listener : mListeners) {
@@ -251,8 +420,8 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp
* Snoozes all current Heads Up Notifications.
*/
public void snooze() {
- for (String key : mAlertEntries.keySet()) {
- AlertEntry entry = getHeadsUpEntry(key);
+ for (String key : mHeadsUpEntryMap.keySet()) {
+ HeadsUpEntry entry = getHeadsUpEntry(key);
String packageName = entry.mEntry.getSbn().getPackageName();
String snoozeKey = snoozeKey(packageName, mUser);
mLogger.logPackageSnoozed(snoozeKey);
@@ -267,7 +436,7 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp
@Nullable
protected HeadsUpEntry getHeadsUpEntry(@NonNull String key) {
- return (HeadsUpEntry) mAlertEntries.get(key);
+ return (HeadsUpEntry) mHeadsUpEntryMap.get(key);
}
/**
@@ -281,11 +450,11 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp
@Nullable
protected HeadsUpEntry getTopHeadsUpEntry() {
- if (mAlertEntries.isEmpty()) {
+ if (mHeadsUpEntryMap.isEmpty()) {
return null;
}
HeadsUpEntry topEntry = null;
- for (AlertEntry entry: mAlertEntries.values()) {
+ for (HeadsUpEntry entry: mHeadsUpEntryMap.values()) {
if (topEntry == null || entry.compareTo(topEntry) < 0) {
topEntry = (HeadsUpEntry) entry;
}
@@ -316,7 +485,7 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp
pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs);
pw.print(" now="); pw.println(mSystemClock.elapsedRealtime());
pw.print(" mUser="); pw.println(mUser);
- for (AlertEntry entry: mAlertEntries.values()) {
+ for (HeadsUpEntry entry: mHeadsUpEntryMap.values()) {
pw.print(" HeadsUpEntry="); pw.println(entry.mEntry);
}
int n = mSnoozedPackages.size();
@@ -335,8 +504,8 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp
}
private boolean hasPinnedNotificationInternal() {
- for (String key : mAlertEntries.keySet()) {
- AlertEntry entry = getHeadsUpEntry(key);
+ for (String key : mHeadsUpEntryMap.keySet()) {
+ HeadsUpEntry entry = getHeadsUpEntry(key);
if (entry.mEntry.isRowPinned()) {
return true;
}
@@ -349,7 +518,7 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp
* @param userUnPinned The unpinned action is trigger by user real operation.
*/
public void unpinAll(boolean userUnPinned) {
- for (String key : mAlertEntries.keySet()) {
+ for (String key : mHeadsUpEntryMap.keySet()) {
HeadsUpEntry entry = getHeadsUpEntry(key);
setEntryPinned(entry, false /* isPinned */);
// maybe it got un sticky
@@ -384,8 +553,8 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp
if (a == null || b == null) {
return Boolean.compare(a == null, b == null);
}
- AlertEntry aEntry = getHeadsUpEntry(a.getKey());
- AlertEntry bEntry = getHeadsUpEntry(b.getKey());
+ HeadsUpEntry aEntry = getHeadsUpEntry(a.getKey());
+ HeadsUpEntry bEntry = getHeadsUpEntry(b.getKey());
if (aEntry == null || bEntry == null) {
return Boolean.compare(aEntry == null, bEntry == null);
}
@@ -419,18 +588,37 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp
}
}
+ /**
+ * Whether or not the entry can be removed currently. If it hasn't been on screen long enough
+ * it should not be removed unless forced
+ * @param key the key to check if removable
+ * @return true if the entry can be removed
+ */
@Override
public boolean canRemoveImmediately(@NonNull String key) {
HeadsUpEntry headsUpEntry = getHeadsUpEntry(key);
if (headsUpEntry != null && headsUpEntry.mUserActionMayIndirectlyRemove) {
return true;
}
- return super.canRemoveImmediately(key);
+ return headsUpEntry == null || headsUpEntry.wasShownLongEnough()
+ || headsUpEntry.mEntry.isRowDismissed();
}
- @NonNull
+ /**
+ * @param key
+ * @return true if the entry is (pinned and expanded) or (has an active remote input)
+ */
@Override
- protected HeadsUpEntry createAlertEntry() {
+ public boolean isSticky(String key) {
+ HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
+ if (headsUpEntry != null) {
+ return headsUpEntry.isSticky();
+ }
+ return false;
+ }
+
+ @NonNull
+ protected HeadsUpEntry createHeadsUpEntry() {
return new HeadsUpEntry();
}
@@ -451,28 +639,82 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp
* This represents a notification and how long it is in a heads up mode. It also manages its
* lifecycle automatically when created.
*/
- protected class HeadsUpEntry extends AlertEntry {
+ protected class HeadsUpEntry implements Comparable<HeadsUpEntry> {
public boolean mRemoteInputActive;
public boolean mUserActionMayIndirectlyRemove;
protected boolean mExpanded;
protected boolean mWasUnpinned;
- @Override
+ @Nullable public NotificationEntry mEntry;
+ public long mPostTime;
+ public long mEarliestRemovalTime;
+
+ @Nullable protected Runnable mRemoveRunnable;
+
+ @Nullable private Runnable mCancelRemoveRunnable;
+
+ public void setEntry(@NonNull final NotificationEntry entry) {
+ setEntry(entry, () -> removeEntry(entry.getKey()));
+ }
+
+ public void setEntry(@NonNull final NotificationEntry entry,
+ @Nullable Runnable removeRunnable) {
+ mEntry = entry;
+ mRemoveRunnable = removeRunnable;
+
+ mPostTime = calculatePostTime();
+ updateEntry(true /* updatePostTime */, "setEntry");
+ }
+
+ /**
+ * Updates an entry's removal time.
+ * @param updatePostTime whether or not to refresh the post time
+ */
+ public void updateEntry(boolean updatePostTime, @Nullable String reason) {
+ mLogger.logUpdateEntry(mEntry, updatePostTime, reason);
+
+ final long now = mSystemClock.elapsedRealtime();
+ mEarliestRemovalTime = now + mMinimumDisplayTime;
+
+ if (updatePostTime) {
+ mPostTime = Math.max(mPostTime, now);
+ }
+
+ if (isSticky()) {
+ removeAutoRemovalCallbacks("updateEntry (sticky)");
+ return;
+ }
+
+ final long finishTime = calculateFinishTime();
+ final long timeLeft = Math.max(finishTime - now, mMinimumDisplayTime);
+ scheduleAutoRemovalCallback(timeLeft, "updateEntry (not sticky)");
+ }
+
+ /**
+ * Whether or not the notification is "sticky" i.e. should stay on screen regardless
+ * of the timer (forever) and should be removed externally.
+ * @return true if the notification is sticky
+ */
public boolean isSticky() {
return (mEntry.isRowPinned() && mExpanded)
|| mRemoteInputActive
|| hasFullScreenIntent(mEntry);
}
- @Override
public boolean isStickyForSomeTime() {
return mEntry.isStickyAndNotDemoted();
}
- @Override
- public int compareTo(@NonNull AlertEntry alertEntry) {
- HeadsUpEntry headsUpEntry = (HeadsUpEntry) alertEntry;
+ /**
+ * Whether the notification has been on screen long enough and can be removed.
+ * @return true if the notification has been on screen long enough
+ */
+ public boolean wasShownLongEnough() {
+ return mEarliestRemovalTime < mSystemClock.elapsedRealtime();
+ }
+
+ public int compareTo(@NonNull HeadsUpEntry headsUpEntry) {
boolean isPinned = mEntry.isRowPinned();
boolean otherPinned = headsUpEntry.mEntry.isRowPinned();
if (isPinned && !otherPinned) {
@@ -503,31 +745,90 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp
return 1;
}
- return super.compareTo(headsUpEntry);
+ if (mPostTime > headsUpEntry.mPostTime) {
+ return -1;
+ } else if (mPostTime == headsUpEntry.mPostTime) {
+ return mEntry.getKey().compareTo(headsUpEntry.mEntry.getKey());
+ } else {
+ return 1;
+ }
}
public void setExpanded(boolean expanded) {
this.mExpanded = expanded;
}
- @Override
public void reset() {
- super.reset();
+ removeAutoRemovalCallbacks("reset()");
+ mEntry = null;
+ mRemoveRunnable = null;
mExpanded = false;
mRemoteInputActive = false;
}
- @Override
+ /**
+ * Clear any pending removal runnables.
+ */
+ public void removeAutoRemovalCallbacks(@Nullable String reason) {
+ final boolean removed = removeAutoRemovalCallbackInternal();
+
+ if (removed) {
+ mLogger.logAutoRemoveCanceled(mEntry, reason);
+ }
+ }
+
+ public void scheduleAutoRemovalCallback(long delayMillis, @NonNull String reason) {
+ if (mRemoveRunnable == null) {
+ Log.wtf(TAG, "scheduleAutoRemovalCallback with no callback set");
+ return;
+ }
+
+ final boolean removed = removeAutoRemovalCallbackInternal();
+
+ if (removed) {
+ mLogger.logAutoRemoveRescheduled(mEntry, delayMillis, reason);
+ } else {
+ mLogger.logAutoRemoveScheduled(mEntry, delayMillis, reason);
+ }
+
+ mCancelRemoveRunnable = mExecutor.executeDelayed(mRemoveRunnable,
+ delayMillis);
+ }
+
+ public boolean removeAutoRemovalCallbackInternal() {
+ final boolean scheduled = (mCancelRemoveRunnable != null);
+
+ if (scheduled) {
+ mCancelRemoveRunnable.run();
+ mCancelRemoveRunnable = null;
+ }
+
+ return scheduled;
+ }
+
+ /**
+ * Remove the entry at the earliest allowed removal time.
+ */
+ public void removeAsSoonAsPossible() {
+ if (mRemoveRunnable != null) {
+ final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime();
+ scheduleAutoRemovalCallback(timeLeft, "removeAsSoonAsPossible");
+ }
+ }
+
+ /**
+ * Calculate what the post time of a notification is at some current time.
+ * @return the post time
+ */
protected long calculatePostTime() {
// The actual post time will be just after the heads-up really slided in
- return super.calculatePostTime() + mTouchAcceptanceDelay;
+ return mSystemClock.elapsedRealtime() + mTouchAcceptanceDelay;
}
/**
* @return When the notification should auto-dismiss itself, based on
* {@link SystemClock#elapsedRealtime()}
*/
- @Override
protected long calculateFinishTime() {
final long duration = getRecommendedHeadsUpTimeoutMs(
isStickyForSomeTime() ? mStickyForSomeTimeAutoDismissTime : mAutoDismissTime);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
index d9527fe98b1a..b8c7e202ce7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
@@ -71,7 +71,7 @@ interface HeadsUpManager : Dumpable {
fun hasPinnedHeadsUp(): Boolean
/** Returns whether or not the given notification is alerting and managed by this manager. */
- fun isAlerting(key: String): Boolean
+ fun isHeadsUpEntry(key: String): Boolean
fun isHeadsUpGoingAway(): Boolean
@@ -213,7 +213,7 @@ class HeadsUpManagerEmptyImpl @Inject constructor() : HeadsUpManager {
override fun getTouchableRegion(): Region? = null
override fun getTopEntry() = null
override fun hasPinnedHeadsUp() = false
- override fun isAlerting(key: String) = false
+ override fun isHeadsUpEntry(key: String) = false
override fun isHeadsUpGoingAway() = false
override fun isSnoozed(packageName: String) = false
override fun isSticky(key: String?) = false
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index d8ef981b6a56..da6bfe84eee7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -48,10 +48,10 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -76,7 +76,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase {
protected MockitoSession mStaticMockSession;
- protected final SceneTestUtils mSceneTestUtils = new SceneTestUtils(this);
+ protected final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
protected @Mock DeviceEntryInteractor mDeviceEntryInteractor;
protected @Mock LockIconView mLockIconView;
protected @Mock AnimatedStateListDrawable mIconDrawable;
@@ -175,7 +175,7 @@ public class LockIconViewControllerBaseTest extends SysuiTestCase {
mPrimaryBouncerInteractor,
mContext,
() -> mDeviceEntryInteractor,
- mSceneTestUtils.getFakeSceneContainerFlags()
+ mKosmos.getFakeSceneContainerFlags()
);
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
index 132bdb55180a..b0887efed4d7 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -373,7 +373,7 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest {
@Test
public void longPress_showBouncer_sceneContainerNotEnabled() {
init(/* useMigrationFlag= */ false);
- mSceneTestUtils.getFakeSceneContainerFlags().setEnabled(false);
+ mKosmos.getFakeSceneContainerFlags().setEnabled(false);
when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
// WHEN longPress
@@ -387,7 +387,7 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest {
@Test
public void longPress_showBouncer() {
init(/* useMigrationFlag= */ false);
- mSceneTestUtils.getFakeSceneContainerFlags().setEnabled(true);
+ mKosmos.getFakeSceneContainerFlags().setEnabled(true);
when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false);
// WHEN longPress
@@ -401,7 +401,7 @@ public class LockIconViewControllerTest extends LockIconViewControllerBaseTest {
@Test
public void longPress_falsingTriggered_doesNotShowBouncer() {
init(/* useMigrationFlag= */ false);
- mSceneTestUtils.getFakeSceneContainerFlags().setEnabled(true);
+ mKosmos.getFakeSceneContainerFlags().setEnabled(true);
when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(true);
// WHEN longPress
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
index 2afb3a15b4be..d86d12303140 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationAnimationControllerTest.java
@@ -640,11 +640,10 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
@Test
public void deleteWindowMagnification_enabling_expectedValuesAndInvokeCallback()
throws RemoteException {
-
enableWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- Mockito.reset(mSpyController);
+ resetMockObjects();
getInstrumentation().runOnMainSync(() -> {
mWindowMagnificationAnimationController.deleteWindowMagnification(
mAnimationCallback2);
@@ -658,6 +657,11 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
mValueAnimator.end();
});
+ // wait for animation returns
+ waitForIdleSync();
+
+ // {@link ValueAnimator.AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)} will only
+ // be triggered once in {@link ValueAnimator#end()}
verify(mSpyController).enableWindowMagnificationInternal(
mScaleCaptor.capture(),
mCenterXCaptor.capture(), mCenterYCaptor.capture(),
@@ -717,7 +721,11 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
deleteWindowMagnificationAndWaitAnimating(mWaitPartialAnimationDuration,
mAnimationCallback);
- deleteWindowMagnificationAndWaitAnimating(0, null);
+ // Verifying that WindowMagnificationController#deleteWindowMagnification is never called
+ // in previous steps
+ verify(mSpyController, never()).deleteWindowMagnification();
+
+ deleteWindowMagnificationWithoutAnimation();
verify(mSpyController).deleteWindowMagnification();
verifyFinalSpec(Float.NaN, Float.NaN, Float.NaN);
@@ -810,6 +818,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
mWindowMagnificationAnimationController.enableWindowMagnification(
targetScale, targetCenterX, targetCenterY, null);
});
+ // wait for animation returns
+ waitForIdleSync();
}
private void enableWindowMagnificationAndWaitAnimating(long duration,
@@ -829,12 +839,16 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
targetScale, targetCenterX, targetCenterY, callback);
advanceTimeBy(duration);
});
+ // wait for animation returns
+ waitForIdleSync();
}
private void deleteWindowMagnificationWithoutAnimation() {
getInstrumentation().runOnMainSync(() -> {
mWindowMagnificationAnimationController.deleteWindowMagnification(null);
});
+ // wait for animation returns
+ waitForIdleSync();
}
private void deleteWindowMagnificationAndWaitAnimating(long duration,
@@ -843,6 +857,8 @@ public class WindowMagnificationAnimationControllerTest extends SysuiTestCase {
mWindowMagnificationAnimationController.deleteWindowMagnification(callback);
advanceTimeBy(duration);
});
+ // wait for animation returns
+ waitForIdleSync();
}
private void verifyStartValue(ArgumentCaptor<Float> captor, float startValue) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 68879a54cfe4..5e5273b779c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -55,8 +55,6 @@ import android.os.Build;
import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -120,10 +118,6 @@ public class MenuViewLayerTest extends SysuiTestCase {
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
- @Rule
- public final CheckFlagsRule mCheckFlagsRule =
- DeviceFlagsValueProvider.createCheckFlagsRule();
-
@Spy
private SysuiTestableContext mSpyContext = getContext();
@Mock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
index 7c626a141a4a..e0c6bbad5635 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/back/domain/interactor/BackActionInteractorTest.kt
@@ -44,6 +44,8 @@ import com.android.systemui.shade.ShadeController
import com.android.systemui.shade.ShadeViewController
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.util.concurrency.FakeExecutor
@@ -56,6 +58,7 @@ import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import org.junit.Before
@@ -89,6 +92,9 @@ class BackActionInteractorTest : SysuiTestCase() {
@Mock private lateinit var onBackInvokedDispatcher: WindowOnBackInvokedDispatcher
@Mock private lateinit var iStatusBarService: IStatusBarService
@Mock private lateinit var headsUpManager: HeadsUpManager
+ private val activeNotificationsRepository = ActiveNotificationListRepository()
+ private val activeNotificationsInteractor =
+ ActiveNotificationsInteractor(activeNotificationsRepository, StandardTestDispatcher())
private val keyguardRepository = FakeKeyguardRepository()
private val windowRootViewVisibilityInteractor: WindowRootViewVisibilityInteractor by lazy {
@@ -98,6 +104,7 @@ class BackActionInteractorTest : SysuiTestCase() {
keyguardRepository,
headsUpManager,
powerInteractor,
+ activeNotificationsInteractor,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 0ee09390d03a..43f7c60721ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.biometrics
import android.app.admin.DevicePolicyManager
+import android.content.pm.PackageManager
import android.hardware.biometrics.BiometricAuthenticator
import android.hardware.biometrics.BiometricConstants
import android.hardware.biometrics.BiometricManager
@@ -79,6 +80,8 @@ import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
+private const val OP_PACKAGE_NAME = "biometric.testapp"
+
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -109,6 +112,8 @@ open class AuthContainerViewTest : SysuiTestCase() {
lateinit var authController: AuthController
@Mock
lateinit var selectedUserInteractor: SelectedUserInteractor
+ @Mock
+ private lateinit var packageManager: PackageManager
private val testScope = TestScope(StandardTestDispatcher())
private val fakeExecutor = FakeExecutor(FakeSystemClock())
@@ -134,6 +139,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
+ private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
private var authContainer: TestAuthContainerView? = null
@@ -156,6 +162,9 @@ open class AuthContainerViewTest : SysuiTestCase() {
selectedUserInteractor,
testScope.backgroundScope,
)
+ // Set up default logo icon
+ whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
+ context.setMockPackageManager(packageManager)
}
@After
@@ -533,6 +542,7 @@ open class AuthContainerViewTest : SysuiTestCase() {
mPromptInfo = PromptInfo().apply {
this.authenticators = authenticators
}
+ mOpPackageName = OP_PACKAGE_NAME
},
testScope.backgroundScope,
fingerprintProps,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
index ec7ce634fd78..b39e09df9d2e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/PromptRepositoryImplTest.kt
@@ -43,6 +43,7 @@ import org.mockito.junit.MockitoJUnit
private const val USER_ID = 9
private const val CHALLENGE = 90L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -102,7 +103,8 @@ class PromptRepositoryImplTest : SysuiTestCase() {
PromptInfo().apply { isConfirmationRequested = case },
USER_ID,
CHALLENGE,
- PromptKind.Biometric()
+ PromptKind.Biometric(),
+ OP_PACKAGE_NAME
)
assertThat(isConfirmationRequired).isEqualTo(case)
@@ -120,7 +122,8 @@ class PromptRepositoryImplTest : SysuiTestCase() {
PromptInfo().apply { isConfirmationRequested = case },
USER_ID,
CHALLENGE,
- PromptKind.Biometric()
+ PromptKind.Biometric(),
+ OP_PACKAGE_NAME
)
assertThat(isConfirmationRequired).isTrue()
@@ -133,17 +136,19 @@ class PromptRepositoryImplTest : SysuiTestCase() {
val kind = PromptKind.Pin
val promptInfo = PromptInfo()
- repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind)
+ repository.setPrompt(promptInfo, USER_ID, CHALLENGE, kind, OP_PACKAGE_NAME)
assertThat(repository.kind.value).isEqualTo(kind)
assertThat(repository.userId.value).isEqualTo(USER_ID)
assertThat(repository.challenge.value).isEqualTo(CHALLENGE)
assertThat(repository.promptInfo.value).isSameInstanceAs(promptInfo)
+ assertThat(repository.opPackageName.value).isEqualTo(OP_PACKAGE_NAME)
repository.unsetPrompt()
assertThat(repository.promptInfo.value).isNull()
assertThat(repository.userId.value).isNull()
assertThat(repository.challenge.value).isNull()
+ assertThat(repository.opPackageName.value).isNull()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
index dcefea28d4c8..8a46c0c6da9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptCredentialInteractorTest.kt
@@ -30,6 +30,7 @@ import org.mockito.junit.MockitoJUnit
private const val USER_ID = 22
private const val OPERATION_ID = 100L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -114,7 +115,8 @@ class PromptCredentialInteractorTest : SysuiTestCase() {
},
kind = kind,
userId = USER_ID,
- challenge = OPERATION_ID
+ challenge = OPERATION_ID,
+ opPackageName = OP_PACKAGE_NAME
)
assertThat(prompt?.title).isEqualTo(title)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index f15b738f3e95..52b42750847a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -51,6 +51,7 @@ private const val NEGATIVE_TEXT = "escape"
private const val USER_ID = 8
private const val CHALLENGE = 999L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -113,13 +114,20 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() {
assertThat(currentPrompt).isNull()
- interactor.useBiometricsForAuthentication(info, USER_ID, CHALLENGE, modalities)
+ interactor.useBiometricsForAuthentication(
+ info,
+ USER_ID,
+ CHALLENGE,
+ modalities,
+ OP_PACKAGE_NAME
+ )
assertThat(currentPrompt).isNotNull()
assertThat(currentPrompt?.title).isEqualTo(TITLE)
assertThat(currentPrompt?.description).isEqualTo(DESCRIPTION)
assertThat(currentPrompt?.subtitle).isEqualTo(SUBTITLE)
assertThat(currentPrompt?.negativeButtonText).isEqualTo(NEGATIVE_TEXT)
+ assertThat(currentPrompt?.opPackageName).isEqualTo(OP_PACKAGE_NAME)
if (allowCredentialFallback) {
assertThat(credentialKind).isSameInstanceAs(PromptKind.Password)
@@ -167,7 +175,7 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() {
assertThat(currentPrompt).isNull()
- interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE)
+ interactor.useCredentialsForAuthentication(info, kind, USER_ID, CHALLENGE, OP_PACKAGE_NAME)
// not using biometrics, should be null with no fallback option
assertThat(currentPrompt).isNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
index bd4973d65006..a46167a423f1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
@@ -1,6 +1,7 @@
package com.android.systemui.biometrics.domain.model
-import android.hardware.biometrics.PromptContentListItemBulletedText
+import android.graphics.Bitmap
+import android.hardware.biometrics.PromptContentItemBulletedText
import android.hardware.biometrics.PromptVerticalListContentView
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -8,6 +9,7 @@ import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
import com.android.systemui.biometrics.promptInfo
import com.android.systemui.biometrics.shared.model.BiometricModalities
import com.android.systemui.biometrics.shared.model.BiometricUserInfo
+import com.android.systemui.res.R
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -15,6 +17,7 @@ import org.junit.runners.JUnit4
private const val USER_ID = 2
private const val OPERATION_ID = 8L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
@SmallTest
@RunWith(JUnit4::class)
@@ -22,19 +25,22 @@ class BiometricPromptRequestTest : SysuiTestCase() {
@Test
fun biometricRequestFromPromptInfo() {
+ val logoRes = R.drawable.ic_cake
val title = "what"
val subtitle = "a"
val description = "request"
val contentView =
PromptVerticalListContentView.Builder()
.setDescription("content description")
- .addListItem(PromptContentListItemBulletedText("content text"))
+ .addListItem(PromptContentItemBulletedText("content item 1"))
+ .addListItem(PromptContentItemBulletedText("content item 2"), 1)
.build()
val fpPros = fingerprintSensorPropertiesInternal().first()
val request =
BiometricPromptRequest.Biometric(
promptInfo(
+ logoRes = logoRes,
title = title,
subtitle = subtitle,
description = description,
@@ -43,8 +49,10 @@ class BiometricPromptRequestTest : SysuiTestCase() {
BiometricUserInfo(USER_ID),
BiometricOperationInfo(OPERATION_ID),
BiometricModalities(fingerprintProperties = fpPros),
+ OP_PACKAGE_NAME,
)
+ assertThat(request.logoRes).isEqualTo(logoRes)
assertThat(request.title).isEqualTo(title)
assertThat(request.subtitle).isEqualTo(subtitle)
assertThat(request.description).isEqualTo(description)
@@ -56,6 +64,23 @@ class BiometricPromptRequestTest : SysuiTestCase() {
}
@Test
+ fun biometricRequestLogoBitmapFromPromptInfo() {
+ val logoBitmap = Bitmap.createBitmap(400, 400, Bitmap.Config.ARGB_8888)
+ val fpPros = fingerprintSensorPropertiesInternal().first()
+ val request =
+ BiometricPromptRequest.Biometric(
+ promptInfo(
+ logoBitmap = logoBitmap,
+ ),
+ BiometricUserInfo(USER_ID),
+ BiometricOperationInfo(OPERATION_ID),
+ BiometricModalities(fingerprintProperties = fpPros),
+ OP_PACKAGE_NAME,
+ )
+ assertThat(request.logoBitmap).isEqualTo(logoBitmap)
+ }
+
+ @Test
fun credentialRequestFromPromptInfo() {
val title = "what"
val subtitle = "a"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index bf61c2e6d32c..3888f2b940b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -16,9 +16,12 @@
package com.android.systemui.biometrics.ui.viewmodel
+import android.content.pm.PackageManager
import android.content.res.Configuration
+import android.graphics.Bitmap
import android.graphics.Point
-import android.hardware.biometrics.PromptContentListItemBulletedText
+import android.graphics.drawable.BitmapDrawable
+import android.hardware.biometrics.PromptContentItemBulletedText
import android.hardware.biometrics.PromptContentView
import android.hardware.biometrics.PromptInfo
import android.hardware.biometrics.PromptVerticalListContentView
@@ -76,6 +79,7 @@ import org.mockito.junit.MockitoJUnit
private const val USER_ID = 4
private const val CHALLENGE = 2L
private const val DELAY = 1000L
+private const val OP_PACKAGE_NAME = "biometric.testapp"
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -88,9 +92,14 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
@Mock private lateinit var authController: AuthController
@Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
@Mock private lateinit var udfpsUtils: UdfpsUtils
+ @Mock private lateinit var packageManager: PackageManager
private val fakeExecutor = FakeExecutor(FakeSystemClock())
private val testScope = TestScope()
+ private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android)
+ private val logoResFromApp = R.drawable.ic_cake
+ private val logoFromApp = context.getDrawable(logoResFromApp)
+ private val logoBitmapFromApp = Bitmap.createBitmap(400, 400, Bitmap.Config.RGB_565)
private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
private lateinit var promptRepository: FakePromptRepository
@@ -140,7 +149,8 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
selector.resetPrompt()
promptContentView =
PromptVerticalListContentView.Builder()
- .addListItem(PromptContentListItemBulletedText("test"))
+ .addListItem(PromptContentItemBulletedText("content item 1"))
+ .addListItem(PromptContentItemBulletedText("content item 2"), 1)
.build()
viewModel =
@@ -152,6 +162,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
udfpsUtils
)
iconViewModel = viewModel.iconViewModel
+
+ // Set up default logo icon and app customized icon
+ whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon)
+ context.setMockPackageManager(packageManager)
+ val resources = context.getOrCreateTestableResources()
+ resources.addOverride(logoResFromApp, logoFromApp)
}
@Test
@@ -1226,6 +1242,26 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
assertThat(contentView).isNull()
}
+ @Test
+ fun defaultLogoIfNoLogoSet() = runGenericTest {
+ val logo by collectLastValue(viewModel.logo)
+ assertThat(logo).isEqualTo(defaultLogoIcon)
+ }
+
+ @Test
+ fun logoResSetByApp() =
+ runGenericTest(logoRes = logoResFromApp) {
+ val logo by collectLastValue(viewModel.logo)
+ assertThat(logo).isEqualTo(logoFromApp)
+ }
+
+ @Test
+ fun logoBitmapSetByApp() =
+ runGenericTest(logoBitmap = logoBitmapFromApp) {
+ val logo by collectLastValue(viewModel.logo)
+ assertThat((logo as BitmapDrawable).bitmap).isEqualTo(logoBitmapFromApp)
+ }
+
/** Asserts that the selected buttons are visible now. */
private suspend fun TestScope.assertButtonsVisible(
tryAgain: Boolean = false,
@@ -1247,6 +1283,8 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
allowCredentialFallback: Boolean = false,
description: String? = null,
contentView: PromptContentView? = null,
+ logoRes: Int = -1,
+ logoBitmap: Bitmap? = null,
block: suspend TestScope.() -> Unit
) {
selector.initializePrompt(
@@ -1256,6 +1294,8 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa
face = testCase.face,
descriptionFromApp = description,
contentViewFromApp = contentView,
+ logoResFromApp = logoRes,
+ logoBitmapFromApp = logoBitmap,
)
// put the view model in the initial authenticating state, unless explicitly skipped
@@ -1433,9 +1473,13 @@ private fun PromptSelectorInteractor.initializePrompt(
allowCredentialFallback: Boolean = false,
descriptionFromApp: String? = null,
contentViewFromApp: PromptContentView? = null,
+ logoResFromApp: Int = -1,
+ logoBitmapFromApp: Bitmap? = null,
) {
val info =
PromptInfo().apply {
+ logoRes = logoResFromApp
+ logoBitmap = logoBitmapFromApp
title = "t"
subtitle = "s"
description = descriptionFromApp
@@ -1444,11 +1488,13 @@ private fun PromptSelectorInteractor.initializePrompt(
isDeviceCredentialAllowed = allowCredentialFallback
isConfirmationRequested = requireConfirmation
}
+
useBiometricsForAuthentication(
info,
USER_ID,
CHALLENGE,
BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face),
+ OP_PACKAGE_NAME,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
index 44c57f34fa1b..134c40da1033 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepositoryTest.kt
@@ -3,8 +3,9 @@ package com.android.systemui.bouncer.data.repository
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.SystemClock
@@ -23,8 +24,8 @@ class KeyguardBouncerRepositoryTest : SysuiTestCase() {
@Mock private lateinit var systemClock: SystemClock
@Mock private lateinit var bouncerLogger: TableLogBuffer
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
lateinit var underTest: KeyguardBouncerRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
deleted file mode 100644
index 30a5497d0a14..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryImplTest.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.communal.data.repository
-
-import android.content.pm.UserInfo
-import android.provider.Settings
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.core.FakeLogBuffer
-import com.android.systemui.settings.FakeUserTracker
-import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.util.settings.FakeSettings
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class CommunalTutorialRepositoryImplTest : SysuiTestCase() {
- private lateinit var secureSettings: FakeSettings
- private lateinit var userRepository: FakeUserRepository
- private lateinit var userTracker: FakeUserTracker
- private lateinit var logBuffer: LogBuffer
-
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- logBuffer = FakeLogBuffer.Factory.create()
- secureSettings = FakeSettings()
- userRepository = FakeUserRepository()
- val listOfUserInfo = listOf(MAIN_USER_INFO)
- userRepository.setUserInfos(listOfUserInfo)
-
- userTracker = FakeUserTracker()
- userTracker.set(
- userInfos = listOfUserInfo,
- selectedUserIndex = 0,
- )
- }
-
- @Test
- fun tutorialSettingState_defaultToNotStarted() =
- testScope.runTest {
- val repository = initCommunalTutorialRepository()
- val tutorialSettingState = collectLastValue(repository.tutorialSettingState)()
- assertThat(tutorialSettingState)
- .isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_NOT_STARTED)
- }
-
- @Test
- fun tutorialSettingState_whenTutorialSettingsUpdatedToStarted() =
- testScope.runTest {
- val repository = initCommunalTutorialRepository()
- setTutorialStateSetting(Settings.Secure.HUB_MODE_TUTORIAL_STARTED)
- val tutorialSettingState = collectLastValue(repository.tutorialSettingState)()
- assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_STARTED)
- }
-
- @Test
- fun tutorialSettingState_whenTutorialSettingsUpdatedToCompleted() =
- testScope.runTest {
- val repository = initCommunalTutorialRepository()
- setTutorialStateSetting(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
- val tutorialSettingState = collectLastValue(repository.tutorialSettingState)()
- assertThat(tutorialSettingState).isEqualTo(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
- }
-
- private fun initCommunalTutorialRepository(): CommunalTutorialRepositoryImpl {
- return CommunalTutorialRepositoryImpl(
- testScope.backgroundScope,
- testDispatcher,
- userRepository,
- secureSettings,
- userTracker,
- logBuffer
- )
- }
-
- private fun setTutorialStateSetting(
- @Settings.Secure.HubModeTutorialState state: Int,
- user: UserInfo = MAIN_USER_INFO
- ) {
- secureSettings.putIntForUser(Settings.Secure.HUB_MODE_TUTORIAL_STATE, state, user.id)
- }
-
- companion object {
- private val MAIN_USER_INFO =
- UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
new file mode 100644
index 000000000000..d397fc202637
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
@@ -0,0 +1,211 @@
+/*
+ * 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.keyboard.stickykeys.ui.viewmodel
+
+import android.hardware.input.InputManager
+import android.hardware.input.StickyModifierState
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository
+import com.android.systemui.keyboard.stickykeys.StickyKeysLogger
+import com.android.systemui.keyboard.stickykeys.data.repository.StickyKeysRepositoryImpl
+import com.android.systemui.keyboard.stickykeys.shared.model.Locked
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.ALT_GR
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.CTRL
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.META
+import com.android.systemui.keyboard.stickykeys.shared.model.ModifierKey.SHIFT
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+
+@SmallTest
+@RunWith(JUnit4::class)
+class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
+
+ private val dispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(dispatcher)
+ private lateinit var viewModel: StickyKeysIndicatorViewModel
+ private val inputManager = mock<InputManager>()
+ private val keyboardRepository = FakeKeyboardRepository()
+ private val captor =
+ ArgumentCaptor.forClass(InputManager.StickyModifierStateListener::class.java)
+
+ @Before
+ fun setup() {
+ val stickyKeysRepository = StickyKeysRepositoryImpl(
+ inputManager,
+ dispatcher,
+ mock<StickyKeysLogger>()
+ )
+ viewModel =
+ StickyKeysIndicatorViewModel(
+ stickyKeysRepository = stickyKeysRepository,
+ keyboardRepository = keyboardRepository,
+ applicationScope = testScope.backgroundScope,
+ )
+ }
+
+ @Test
+ fun startsListeningToStickyKeysOnlyWhenKeyboardIsConnected() {
+ testScope.runTest {
+ collectLastValue(viewModel.indicatorContent)
+ runCurrent()
+ verifyZeroInteractions(inputManager)
+
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ runCurrent()
+
+ verify(inputManager)
+ .registerStickyModifierStateListener(
+ any(),
+ any(InputManager.StickyModifierStateListener::class.java)
+ )
+ }
+ }
+
+ @Test
+ fun stopsListeningToStickyKeysWhenKeyboardDisconnects() {
+ testScope.runTest {
+ collectLastValue(viewModel.indicatorContent)
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+ runCurrent()
+
+ keyboardRepository.setIsAnyKeyboardConnected(false)
+ runCurrent()
+
+ verify(inputManager).unregisterStickyModifierStateListener(any())
+ }
+ }
+
+ @Test
+ fun emitsStickyKeysListWhenStickyKeyIsPressed() {
+ testScope.runTest {
+ val stickyKeys by collectLastValue(viewModel.indicatorContent)
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+
+ setStickyKeys(mapOf(ALT to false))
+
+ assertThat(stickyKeys).isEqualTo(mapOf(ALT to Locked(false)))
+ }
+ }
+
+ @Test
+ fun emitsEmptyListWhenNoStickyKeysAreActive() {
+ testScope.runTest {
+ val stickyKeys by collectLastValue(viewModel.indicatorContent)
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+
+ setStickyKeys(emptyMap())
+
+ assertThat(stickyKeys).isEqualTo(emptyMap<ModifierKey, Locked>())
+ }
+ }
+
+ @Test
+ fun passesAllStickyKeysToDialog() {
+ testScope.runTest {
+ val stickyKeys by collectLastValue(viewModel.indicatorContent)
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+
+ setStickyKeys(mapOf(
+ ALT to false,
+ META to false,
+ SHIFT to false))
+
+ assertThat(stickyKeys).isEqualTo(mapOf(
+ ALT to Locked(false),
+ META to Locked(false),
+ SHIFT to Locked(false),
+ ))
+ }
+ }
+
+ @Test
+ fun showsOnlyLockedStateIfKeyIsStickyAndLocked() {
+ testScope.runTest {
+ val stickyKeys by collectLastValue(viewModel.indicatorContent)
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+
+ setStickyKeys(mapOf(
+ ALT to false,
+ ALT to true))
+
+ assertThat(stickyKeys).isEqualTo(mapOf(ALT to Locked(true)))
+ }
+ }
+
+ @Test
+ fun doesNotChangeOrderOfKeysIfTheyBecomeLocked() {
+ testScope.runTest {
+ val stickyKeys by collectLastValue(viewModel.indicatorContent)
+ keyboardRepository.setIsAnyKeyboardConnected(true)
+
+ setStickyKeys(mapOf(
+ META to false,
+ SHIFT to false, // shift is sticky but not locked
+ CTRL to false))
+ val previousShiftIndex = stickyKeys?.toList()?.indexOf(SHIFT to Locked(false))
+
+ setStickyKeys(mapOf(
+ SHIFT to false,
+ SHIFT to true, // shift is now locked
+ META to false,
+ CTRL to false))
+ assertThat(stickyKeys?.toList()?.indexOf(SHIFT to Locked(true)))
+ .isEqualTo(previousShiftIndex)
+ }
+ }
+
+ private fun TestScope.setStickyKeys(keys: Map<ModifierKey, Boolean>) {
+ runCurrent()
+ verify(inputManager).registerStickyModifierStateListener(any(), captor.capture())
+ captor.value.onStickyModifierStateChanged(TestStickyModifierState(keys))
+ runCurrent()
+ }
+
+ private class TestStickyModifierState(private val keys: Map<ModifierKey, Boolean>) :
+ StickyModifierState() {
+
+ private fun isOn(key: ModifierKey) = keys.any { it.key == key && !it.value }
+ private fun isLocked(key: ModifierKey) = keys.any { it.key == key && it.value }
+
+ override fun isAltGrModifierLocked() = isLocked(ALT_GR)
+ override fun isAltGrModifierOn() = isOn(ALT_GR)
+ override fun isAltModifierLocked() = isLocked(ALT)
+ override fun isAltModifierOn() = isOn(ALT)
+ override fun isCtrlModifierLocked() = isLocked(CTRL)
+ override fun isCtrlModifierOn() = isOn(CTRL)
+ override fun isMetaModifierLocked() = isLocked(META)
+ override fun isMetaModifierOn() = isOn(META)
+ override fun isShiftModifierLocked() = isLocked(SHIFT)
+ override fun isShiftModifierOn() = isOn(SHIFT)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 0ea4e9f8d416..8b6611f339c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1342,14 +1342,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
runCurrent()
- // WHEN the keyguard is occluded and aod ends
+ // WHEN the keyguard is occluded
keyguardRepository.setKeyguardOccluded(true)
- keyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(
- from = DozeStateModel.DOZE_AOD,
- to = DozeStateModel.FINISH,
- )
- )
runCurrent()
val info =
@@ -1366,6 +1360,30 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() {
}
@Test
+ fun aodToPrimaryBouncer() =
+ testScope.runTest {
+ // GIVEN a prior transition has run to AOD
+ runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.AOD)
+ runCurrent()
+
+ // WHEN the primary bouncer is set to show
+ bouncerRepository.setPrimaryShow(true)
+ runCurrent()
+
+ val info =
+ withArgCaptor<TransitionInfo> {
+ verify(transitionRepository).startTransition(capture())
+ }
+ // THEN a transition to OCCLUDED should occur
+ assertThat(info.ownerName).isEqualTo("FromAodTransitionInteractor")
+ assertThat(info.from).isEqualTo(KeyguardState.AOD)
+ assertThat(info.to).isEqualTo(KeyguardState.PRIMARY_BOUNCER)
+ assertThat(info.animator).isNotNull()
+
+ coroutineContext.cancelChildren()
+ }
+
+ @Test
fun lockscreenToOccluded_fromCameraGesture() =
testScope.runTest {
// GIVEN a prior transition has run to LOCKSCREEN
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
index a4d217f1af79..5dd37ae46ee8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinderTest.kt
@@ -21,13 +21,17 @@ import androidx.constraintlayout.helper.widget.Layer
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardClockSwitch.LARGE
+import com.android.keyguard.KeyguardClockSwitch.SMALL
import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.plugins.clocks.ClockConfig
import com.android.systemui.plugins.clocks.ClockController
import com.android.systemui.plugins.clocks.ClockFaceController
import com.android.systemui.plugins.clocks.ClockFaceLayout
import com.android.systemui.util.mockito.whenever
import kotlin.test.Test
+import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.Mock
@@ -48,30 +52,58 @@ class KeyguardClockViewBinderTest : SysuiTestCase() {
@Mock private lateinit var smallClockView: View
@Mock private lateinit var smallClockFaceLayout: ClockFaceLayout
@Mock private lateinit var largeClockFaceLayout: ClockFaceLayout
+ @Mock private lateinit var clockViewModel: KeyguardClockViewModel
+ private val clockSize = MutableStateFlow(LARGE)
+ private val currentClock: MutableStateFlow<ClockController?> = MutableStateFlow(null)
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
+ whenever(clockViewModel.clockSize).thenReturn(clockSize)
+ whenever(clockViewModel.currentClock).thenReturn(currentClock)
+ whenever(clockViewModel.burnInLayer).thenReturn(burnInLayer)
+ }
+
+ @Test
+ fun addClockViews_WeatherClock() {
+ setupWeatherClock()
+ KeyguardClockViewBinder.addClockViews(clock, rootView)
+ verify(rootView).addView(smallClockView)
+ verify(rootView).addView(largeClockView)
}
@Test
fun addClockViews_nonWeatherClock() {
setupNonWeatherClock()
- KeyguardClockViewBinder.addClockViews(clock, rootView, burnInLayer)
+ KeyguardClockViewBinder.addClockViews(clock, rootView)
verify(rootView).addView(smallClockView)
verify(rootView).addView(largeClockView)
- verify(burnInLayer).addView(smallClockView)
+ }
+ @Test
+ fun addClockViewsToBurnInLayer_LargeWeatherClock() {
+ setupWeatherClock()
+ clockSize.value = LARGE
+ KeyguardClockViewBinder.updateBurnInLayer(rootView, clockViewModel)
+ verify(burnInLayer).removeView(smallClockView)
+ verify(burnInLayer).addView(largeClockView)
+ }
+
+ @Test
+ fun addClockViewsToBurnInLayer_LargeNonWeatherClock() {
+ setupNonWeatherClock()
+ clockSize.value = LARGE
+ KeyguardClockViewBinder.updateBurnInLayer(rootView, clockViewModel)
+ verify(burnInLayer).removeView(smallClockView)
verify(burnInLayer, never()).addView(largeClockView)
}
@Test
- fun addClockViews_WeatherClock() {
- setupWeatherClock()
- KeyguardClockViewBinder.addClockViews(clock, rootView, burnInLayer)
- verify(rootView).addView(smallClockView)
- verify(rootView).addView(largeClockView)
+ fun addClockViewsToBurnInLayer_SmallClock() {
+ setupNonWeatherClock()
+ clockSize.value = SMALL
+ KeyguardClockViewBinder.updateBurnInLayer(rootView, clockViewModel)
verify(burnInLayer).addView(smallClockView)
- verify(burnInLayer).addView(largeClockView)
+ verify(burnInLayer).removeView(largeClockView)
}
private fun setupWeatherClock() {
@@ -99,5 +131,7 @@ class KeyguardClockViewBinderTest : SysuiTestCase() {
whenever(clock.smallClock).thenReturn(smallClock)
whenever(largeClock.layout).thenReturn(largeClockFaceLayout)
whenever(smallClock.layout).thenReturn(smallClockFaceLayout)
+ whenever(clockViewModel.clock).thenReturn(clock)
+ currentClock.value = clock
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index 070a0ccd1232..57b555989166 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -22,6 +22,7 @@ import android.content.res.Resources
import androidx.constraintlayout.widget.ConstraintSet
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.KeyguardClockInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.res.R
@@ -31,6 +32,8 @@ import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
+import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -46,6 +49,8 @@ class ClockSectionTest : SysuiTestCase() {
@Mock private lateinit var keyguardClockInteractor: KeyguardClockInteractor
@Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel
@Mock private lateinit var splitShadeStateController: SplitShadeStateController
+ @Mock private lateinit var blueprintInteractor: Lazy<KeyguardBlueprintInteractor>
+ private val clockShouldBeCentered: MutableStateFlow<Boolean> = MutableStateFlow(true)
private lateinit var underTest: ClockSection
@@ -104,12 +109,15 @@ class ClockSectionTest : SysuiTestCase() {
whenever(packageManager.getResourcesForApplication(anyString())).thenReturn(remoteResources)
mContext.setMockPackageManager(packageManager)
+ whenever(keyguardClockViewModel.clockShouldBeCentered).thenReturn(clockShouldBeCentered)
+
underTest =
ClockSection(
keyguardClockInteractor,
keyguardClockViewModel,
mContext,
splitShadeStateController,
+ blueprintInteractor
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
index 28da957d31b0..deb3a83fcbee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
@@ -26,6 +26,8 @@ import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardSmartspaceInteractor
import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
import com.android.systemui.res.R
@@ -34,7 +36,8 @@ import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.StateFlow
+import dagger.Lazy
+import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -50,7 +53,8 @@ class SmartspaceSectionTest : SysuiTestCase() {
@Mock private lateinit var keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel
@Mock private lateinit var lockscreenSmartspaceController: LockscreenSmartspaceController
@Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
- @Mock private lateinit var hasCustomWeatherDataDisplay: StateFlow<Boolean>
+ @Mock private lateinit var keyguardSmartspaceInteractor: KeyguardSmartspaceInteractor
+ @Mock private lateinit var blueprintInteractor: Lazy<KeyguardBlueprintInteractor>
private val smartspaceView = View(mContext).also { it.id = sharedR.id.bc_smartspace_view }
private val weatherView = View(mContext).also { it.id = sharedR.id.weather_smartspace_view }
@@ -58,17 +62,22 @@ class SmartspaceSectionTest : SysuiTestCase() {
private lateinit var constraintLayout: ConstraintLayout
private lateinit var constraintSet: ConstraintSet
+ private val clockShouldBeCentered = MutableStateFlow(false)
+ private val hasCustomWeatherDataDisplay = MutableStateFlow(false)
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
mSetFlagsRule.enableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
underTest =
SmartspaceSection(
+ mContext,
keyguardClockViewModel,
keyguardSmartspaceViewModel,
- mContext,
+ keyguardSmartspaceInteractor,
lockscreenSmartspaceController,
keyguardUnlockAnimationController,
+ blueprintInteractor
)
constraintLayout = ConstraintLayout(mContext)
whenever(lockscreenSmartspaceController.buildAndConnectView(any()))
@@ -78,6 +87,7 @@ class SmartspaceSectionTest : SysuiTestCase() {
whenever(lockscreenSmartspaceController.buildAndConnectDateView(any())).thenReturn(dateView)
whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay)
.thenReturn(hasCustomWeatherDataDisplay)
+ whenever(keyguardClockViewModel.clockShouldBeCentered).thenReturn(clockShouldBeCentered)
constraintSet = ConstraintSet()
}
@@ -115,7 +125,7 @@ class SmartspaceSectionTest : SysuiTestCase() {
fun testConstraintsWhenNotHasCustomWeatherDataDisplay() {
whenever(keyguardSmartspaceViewModel.isSmartspaceEnabled).thenReturn(true)
whenever(keyguardSmartspaceViewModel.isDateWeatherDecoupled).thenReturn(true)
- whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(false)
+ hasCustomWeatherDataDisplay.value = false
underTest.addViews(constraintLayout)
underTest.applyConstraints(constraintSet)
assertWeatherSmartspaceConstrains(constraintSet)
@@ -129,7 +139,7 @@ class SmartspaceSectionTest : SysuiTestCase() {
@Test
fun testConstraintsWhenHasCustomWeatherDataDisplay() {
- whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(true)
+ hasCustomWeatherDataDisplay.value = true
underTest.addViews(constraintLayout)
underTest.applyConstraints(constraintSet)
assertWeatherSmartspaceConstrains(constraintSet)
@@ -140,7 +150,7 @@ class SmartspaceSectionTest : SysuiTestCase() {
@Test
fun testNormalDateWeatherVisibility() {
- whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(false)
+ hasCustomWeatherDataDisplay.value = false
whenever(keyguardSmartspaceViewModel.isWeatherEnabled).thenReturn(true)
underTest.addViews(constraintLayout)
underTest.applyConstraints(constraintSet)
@@ -153,7 +163,7 @@ class SmartspaceSectionTest : SysuiTestCase() {
}
@Test
fun testCustomDateWeatherVisibility() {
- whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay.value).thenReturn(true)
+ hasCustomWeatherDataDisplay.value = true
underTest.addViews(constraintLayout)
underTest.applyConstraints(constraintSet)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 73885f4d428d..6be92756ba8a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -104,18 +104,19 @@ class MediaHierarchyManagerTest : SysuiTestCase() {
private lateinit var dreamOverlayCallback:
ArgumentCaptor<(DreamOverlayStateController.Callback)>
@JvmField @Rule val mockito = MockitoJUnit.rule()
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
private lateinit var mediaHierarchyManager: MediaHierarchyManager
private lateinit var isQsBypassingShade: MutableStateFlow<Boolean>
private lateinit var mediaFrame: ViewGroup
private val configurationController = FakeConfigurationController()
- private val communalRepository = FakeCommunalRepository(isCommunalEnabled = true)
+ private val communalRepository =
+ FakeCommunalRepository(applicationScope = testScope.backgroundScope)
private val communalInteractor =
CommunalInteractorFactory.create(communalRepository = communalRepository).communalInteractor
private val settings = FakeSettings()
private lateinit var testableLooper: TestableLooper
private lateinit var fakeHandler: FakeHandler
- private val testDispatcher = StandardTestDispatcher()
- private val testScope = TestScope(testDispatcher)
@Before
fun setup() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
index 45f0a8c62125..44c411fdb1d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/FakeMediaProjectionManager.kt
@@ -66,6 +66,11 @@ class FakeMediaProjectionManager {
private const val DEFAULT_PACKAGE_NAME = "com.media.projection.test"
private val DEFAULT_USER_HANDLE = UserHandle.getUserHandleForUid(UserHandle.myUserId())
- private val DEFAULT_INFO = MediaProjectionInfo(DEFAULT_PACKAGE_NAME, DEFAULT_USER_HANDLE)
+ private val DEFAULT_INFO =
+ MediaProjectionInfo(
+ DEFAULT_PACKAGE_NAME,
+ DEFAULT_USER_HANDLE,
+ /* launchCookie = */ null
+ )
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt
new file mode 100644
index 000000000000..60eb3aec190f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import android.testing.AndroidTestingRunner
+import android.view.KeyEvent
+import android.view.KeyEvent.KEYCODE_DPAD_LEFT
+import android.view.View
+import androidx.core.util.Consumer
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LeftRightArrowPressedListenerTest : SysuiTestCase() {
+
+ private lateinit var underTest: LeftRightArrowPressedListener
+ private val callback =
+ object : Consumer<Int> {
+ var lastValue: Int? = null
+
+ override fun accept(keyCode: Int?) {
+ lastValue = keyCode
+ }
+ }
+
+ private val view = View(context)
+
+ @Before
+ fun setUp() {
+ underTest = LeftRightArrowPressedListener.createAndRegisterListenerForView(view)
+ underTest.setArrowKeyPressedListener(callback)
+ }
+
+ @Test
+ fun shouldTriggerCallback_whenArrowUpReceived_afterArrowDownReceived() {
+ underTest.sendKey(KeyEvent.ACTION_DOWN, KEYCODE_DPAD_LEFT)
+
+ underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT)
+
+ assertThat(callback.lastValue).isEqualTo(KEYCODE_DPAD_LEFT)
+ }
+
+ @Test
+ fun shouldNotTriggerCallback_whenKeyUpReceived_ifKeyDownNotReceived() {
+ underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT)
+
+ assertThat(callback.lastValue).isNull()
+ }
+
+ @Test
+ fun shouldNotTriggerCallback_whenKeyUpReceived_ifKeyDownWasRepeated() {
+ underTest.sendKeyWithRepeat(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT, repeat = 2)
+ underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT)
+
+ assertThat(callback.lastValue).isNull()
+ }
+
+ @Test
+ fun shouldNotTriggerCallback_whenKeyUpReceived_ifKeyDownReceivedBeforeLosingFocus() {
+ underTest.sendKey(KeyEvent.ACTION_DOWN, KEYCODE_DPAD_LEFT)
+ underTest.onFocusChange(view, hasFocus = false)
+ underTest.onFocusChange(view, hasFocus = true)
+
+ underTest.sendKey(KeyEvent.ACTION_UP, KEYCODE_DPAD_LEFT)
+
+ assertThat(callback.lastValue).isNull()
+ }
+
+ private fun LeftRightArrowPressedListener.sendKey(action: Int, keyCode: Int) {
+ onKey(view, keyCode, KeyEvent(action, keyCode))
+ }
+
+ private fun LeftRightArrowPressedListener.sendKeyWithRepeat(
+ action: Int,
+ keyCode: Int,
+ repeat: Int
+ ) {
+ val keyEvent =
+ KeyEvent(
+ /* downTime= */ 0L,
+ /* eventTime= */ 0L,
+ /* action= */ action,
+ /* code= */ KEYCODE_DPAD_LEFT,
+ /* repeat= */ repeat
+ )
+ onKey(view, keyCode, keyEvent)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
index db9e548e74c8..8ef3f57103a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt
@@ -2,11 +2,13 @@ package com.android.systemui.qs
import android.content.Context
import android.testing.AndroidTestingRunner
-import android.view.KeyEvent
import android.view.View
import android.widget.Scroller
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.PageIndicator.PageScrollActionListener
+import com.android.systemui.qs.PageIndicator.PageScrollActionListener.LEFT
+import com.android.systemui.qs.PageIndicator.PageScrollActionListener.RIGHT
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
@@ -22,7 +24,7 @@ import org.mockito.MockitoAnnotations
class PagedTileLayoutTest : SysuiTestCase() {
@Mock private lateinit var pageIndicator: PageIndicator
- @Captor private lateinit var captor: ArgumentCaptor<View.OnKeyListener>
+ @Captor private lateinit var captor: ArgumentCaptor<PageScrollActionListener>
private lateinit var pageTileLayout: TestPagedTileLayout
private lateinit var scroller: Scroller
@@ -32,7 +34,7 @@ class PagedTileLayoutTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
pageTileLayout = TestPagedTileLayout(mContext)
pageTileLayout.setPageIndicator(pageIndicator)
- verify(pageIndicator).setOnKeyListener(captor.capture())
+ verify(pageIndicator).setPageScrollActionListener(captor.capture())
setViewWidth(pageTileLayout, width = PAGE_WIDTH)
scroller = pageTileLayout.mScroller
}
@@ -43,28 +45,27 @@ class PagedTileLayoutTest : SysuiTestCase() {
}
@Test
- fun scrollsRight_afterRightArrowPressed_whenFocusOnPagerIndicator() {
+ fun scrollsRight_afterRightScrollActionTriggered() {
pageTileLayout.currentPageIndex = 0
- sendUpEvent(KeyEvent.KEYCODE_DPAD_RIGHT)
+ sendScrollActionEvent(RIGHT)
assertThat(scroller.isFinished).isFalse() // aka we're scrolling
assertThat(scroller.finalX).isEqualTo(scroller.currX + PAGE_WIDTH)
}
@Test
- fun scrollsLeft_afterLeftArrowPressed_whenFocusOnPagerIndicator() {
+ fun scrollsLeft_afterLeftScrollActionTriggered() {
pageTileLayout.currentPageIndex = 1 // we won't scroll left if we're on the first page
- sendUpEvent(KeyEvent.KEYCODE_DPAD_LEFT)
+ sendScrollActionEvent(LEFT)
assertThat(scroller.isFinished).isFalse() // aka we're scrolling
assertThat(scroller.finalX).isEqualTo(scroller.currX - PAGE_WIDTH)
}
- private fun sendUpEvent(keyCode: Int) {
- val event = KeyEvent(KeyEvent.ACTION_UP, keyCode)
- captor.value.onKey(pageIndicator, keyCode, event)
+ private fun sendScrollActionEvent(@PageScrollActionListener.Direction direction: Int) {
+ captor.value.onScrollActionTriggered(direction)
}
/**
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index c7118066a276..1cb3bf6c7100 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -393,23 +393,23 @@ class FooterActionsViewModelTest : SysuiTestCase() {
}
@Test
- fun backgroundAlpha_inSplitShade_followsExpansion_with_0_99_delay() {
+ fun backgroundAlpha_inSplitShade_followsExpansion_with_0_15_delay() {
val underTest = utils.footerActionsViewModel()
val floatTolerance = 0.01f
underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = true)
assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
- underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = true)
+ underTest.onQuickSettingsExpansionChanged(0.1f, isInSplitShade = true)
assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
- underTest.onQuickSettingsExpansionChanged(0.9f, isInSplitShade = true)
+ underTest.onQuickSettingsExpansionChanged(0.14f, isInSplitShade = true)
assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
- underTest.onQuickSettingsExpansionChanged(0.991f, isInSplitShade = true)
+ underTest.onQuickSettingsExpansionChanged(0.235f, isInSplitShade = true)
assertThat(underTest.backgroundAlpha.value).isWithin(floatTolerance).of(0.1f)
- underTest.onQuickSettingsExpansionChanged(0.995f, isInSplitShade = true)
+ underTest.onQuickSettingsExpansionChanged(0.575f, isInSplitShade = true)
assertThat(underTest.backgroundAlpha.value).isWithin(floatTolerance).of(0.5f)
underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
index 33066d2092b8..9563cebf898b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/bluetooth/BluetoothTileDialogViewModelTest.kt
@@ -162,13 +162,15 @@ class BluetoothTileDialogViewModelTest : SysuiTestCase() {
@Test
fun testStartSettingsActivity_activityLaunched_dialogDismissed() {
- `when`(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
- bluetoothTileDialogViewModel.showDialog(context, null)
+ testScope.runTest {
+ `when`(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
+ bluetoothTileDialogViewModel.showDialog(context, null)
- val clickedView = View(context)
- bluetoothTileDialogViewModel.onPairNewDeviceClicked(clickedView)
+ val clickedView = View(context)
+ bluetoothTileDialogViewModel.onPairNewDeviceClicked(clickedView)
- verify(uiEventLogger).log(BluetoothTileDialogUiEvent.PAIR_NEW_DEVICE_CLICKED)
- verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt(), nullable())
+ verify(uiEventLogger).log(BluetoothTileDialogUiEvent.PAIR_NEW_DEVICE_CLICKED)
+ verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt(), nullable())
+ }
}
}
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 49579f6f46b4..b3e386e69905 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -113,6 +113,7 @@ import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransition
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.KeyguardMediaController;
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
@@ -125,7 +126,6 @@ import com.android.systemui.plugins.qs.QS;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.qs.QSFragmentLegacy;
import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.shade.data.repository.ShadeAnimationRepository;
@@ -355,8 +355,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
protected FakeKeyguardRepository mFakeKeyguardRepository;
protected KeyguardInteractor mKeyguardInteractor;
protected ShadeAnimationInteractor mShadeAnimationInteractor;
- protected SceneTestUtils mUtils = new SceneTestUtils(this);
- protected TestScope mTestScope = mUtils.getTestScope();
+ protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ protected TestScope mTestScope = mKosmos.getTestScope();
protected ShadeInteractor mShadeInteractor;
protected PowerInteractor mPowerInteractor;
protected NotificationPanelViewController.TouchHandler mTouchHandler;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 971c8a3d1c05..a894f877fe3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -73,11 +73,11 @@ import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions;
import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.res.R;
import com.android.systemui.scene.FakeWindowRootViewComponent;
-import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.scene.data.repository.SceneContainerRepository;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
@@ -150,8 +150,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
private final Executor mMainExecutor = MoreExecutors.directExecutor();
private final Executor mBackgroundExecutor = MoreExecutors.directExecutor();
- private final SceneTestUtils mUtils = new SceneTestUtils(this);
- private final TestScope mTestScope = mUtils.getTestScope();
+ private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ private final TestScope mTestScope = mKosmos.getTestScope();
private ShadeInteractor mShadeInteractor;
private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
@@ -178,15 +178,15 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
FakeShadeRepository shadeRepository = new FakeShadeRepository();
- mScreenOffAnimationController = mUtils.getScreenOffAnimationController();
- mStatusBarStateController = spy(mUtils.getStatusBarStateController());
- PowerInteractor powerInteractor = mUtils.powerInteractor();
+ mScreenOffAnimationController = mKosmos.getScreenOffAnimationController();
+ mStatusBarStateController = spy(mKosmos.getStatusBarStateController());
+ PowerInteractor powerInteractor = mKosmos.getPowerInteractor();
SceneInteractor sceneInteractor = new SceneInteractor(
mTestScope.getBackgroundScope(),
new SceneContainerRepository(
mTestScope.getBackgroundScope(),
- mUtils.fakeSceneContainerConfig()),
+ mKosmos.getFakeSceneContainerConfig()),
powerInteractor,
mock(SceneLogger.class));
@@ -219,8 +219,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
keyguardTransitionRepository,
keyguardTransitionInteractor,
mTestScope.getBackgroundScope(),
- mUtils.getTestDispatcher(),
- mUtils.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
keyguardInteractor,
featureFlags,
shadeRepository,
@@ -245,8 +245,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
keyguardTransitionRepository,
keyguardTransitionInteractor,
mTestScope.getBackgroundScope(),
- mUtils.getTestDispatcher(),
- mUtils.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
keyguardInteractor,
featureFlags,
mKeyguardSecurityModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index ca68fd867b39..cbd4d2bfe377 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -61,6 +61,7 @@ import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions;
import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
@@ -68,7 +69,6 @@ import com.android.systemui.plugins.qs.QS;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.qs.QSFragmentLegacy;
import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.scene.data.repository.SceneContainerRepository;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
@@ -131,8 +131,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase {
protected QuickSettingsController mQsController;
- protected SceneTestUtils mUtils = new SceneTestUtils(this);
- protected TestScope mTestScope = mUtils.getTestScope();
+ protected KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ protected TestScope mTestScope = mKosmos.getTestScope();
@Mock protected Resources mResources;
@Mock protected KeyguardBottomAreaView mQsFrame;
@@ -203,8 +203,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase {
public void setup() {
MockitoAnnotations.initMocks(this);
when(mPanelViewControllerLazy.get()).thenReturn(mNotificationPanelViewController);
- mStatusBarStateController = mUtils.getStatusBarStateController();
- mInteractionJankMonitor = mUtils.getInteractionJankMonitor();
+ mStatusBarStateController = mKosmos.getStatusBarStateController();
+ mInteractionJankMonitor = mKosmos.getInteractionJankMonitor();
FakeDeviceProvisioningRepository deviceProvisioningRepository =
new FakeDeviceProvisioningRepository();
@@ -212,13 +212,13 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase {
FakeFeatureFlagsClassic featureFlags = new FakeFeatureFlagsClassic();
FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
- PowerInteractor powerInteractor = mUtils.powerInteractor();
+ PowerInteractor powerInteractor = mKosmos.getPowerInteractor();
SceneInteractor sceneInteractor = new SceneInteractor(
mTestScope.getBackgroundScope(),
new SceneContainerRepository(
mTestScope.getBackgroundScope(),
- mUtils.fakeSceneContainerConfig()),
+ mKosmos.getFakeSceneContainerConfig()),
powerInteractor,
mock(SceneLogger.class));
@@ -250,8 +250,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase {
keyguardTransitionRepository,
keyguardTransitionInteractor,
mTestScope.getBackgroundScope(),
- mUtils.getTestDispatcher(),
- mUtils.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
keyguardInteractor,
featureFlags,
mShadeRepository,
@@ -276,8 +276,8 @@ public class QuickSettingsControllerBaseTest extends SysuiTestCase {
keyguardTransitionRepository,
keyguardTransitionInteractor,
mTestScope.getBackgroundScope(),
- mUtils.getTestDispatcher(),
- mUtils.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
keyguardInteractor,
featureFlags,
mock(KeyguardSecurityModel.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 215f8b1c462f..c4911a41b4a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -33,6 +33,8 @@ import com.android.systemui.scene.data.repository.WindowRootViewVisibilityReposi
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.row.NotificationGutsManager
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -45,6 +47,7 @@ import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import dagger.Lazy
+import kotlinx.coroutines.test.StandardTestDispatcher
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -59,6 +62,8 @@ import org.mockito.MockitoAnnotations
@SmallTest
class ShadeControllerImplTest : SysuiTestCase() {
private val executor = FakeExecutor(FakeSystemClock())
+ private val testDispatcher = StandardTestDispatcher()
+ private val activeNotificationsRepository = ActiveNotificationListRepository()
@Mock private lateinit var commandQueue: CommandQueue
@Mock private lateinit var keyguardStateController: KeyguardStateController
@@ -84,6 +89,7 @@ class ShadeControllerImplTest : SysuiTestCase() {
FakeKeyguardRepository(),
headsUpManager,
PowerInteractorFactory.create().powerInteractor,
+ ActiveNotificationsInteractor(activeNotificationsRepository, testDispatcher)
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
deleted file mode 100644
index e1d928272e16..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ /dev/null
@@ -1,227 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package com.android.systemui.statusbar;
-
-import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.spy;
-
-import android.app.ActivityManager;
-import android.app.Notification;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.res.R;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.FakeGlobalSettings;
-import com.android.systemui.util.time.FakeSystemClock;
-import com.android.systemui.util.time.SystemClock;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnit;
-import org.mockito.junit.MockitoRule;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class AlertingNotificationManagerTest extends SysuiTestCase {
- @Rule
- public MockitoRule rule = MockitoJUnit.rule();
-
- private static final String TEST_PACKAGE_NAME = "test";
- private static final int TEST_UID = 0;
-
- protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
- protected static final int TEST_AUTO_DISMISS_TIME = 600;
- protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
- // Number of notifications to use in tests requiring multiple notifications
- private static final int TEST_NUM_NOTIFICATIONS = 4;
-
- protected final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
- protected final FakeSystemClock mSystemClock = new FakeSystemClock();
- protected final FakeExecutor mExecutor = new FakeExecutor(mSystemClock);
-
- @Mock protected ExpandableNotificationRow mRow;
-
- static {
- assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
- assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
- }
-
- private static class TestableAlertingNotificationManager extends AlertingNotificationManager {
- private AlertEntry mLastCreatedEntry;
-
- private TestableAlertingNotificationManager(SystemClock systemClock,
- DelayableExecutor executor) {
- super(new HeadsUpManagerLogger(logcatLogBuffer()), systemClock, executor);
- mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
- mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
- mStickyForSomeTimeAutoDismissTime = TEST_STICKY_AUTO_DISMISS_TIME;
- }
-
- @Override
- protected void onAlertEntryAdded(AlertEntry alertEntry) {}
-
- @Override
- protected void onAlertEntryRemoved(AlertEntry alertEntry) {}
-
- @Override
- protected AlertEntry createAlertEntry() {
- mLastCreatedEntry = spy(super.createAlertEntry());
- return mLastCreatedEntry;
- }
-
- @Override
- public int getContentFlag() {
- return FLAG_CONTENT_VIEW_CONTRACTED;
- }
- }
-
- protected AlertingNotificationManager createAlertingNotificationManager() {
- return new TestableAlertingNotificationManager(mSystemClock, mExecutor);
- }
-
- protected StatusBarNotification createSbn(int id, Notification n) {
- return new StatusBarNotification(
- TEST_PACKAGE_NAME /* pkg */,
- TEST_PACKAGE_NAME,
- id,
- null /* tag */,
- TEST_UID,
- 0 /* initialPid */,
- n,
- new UserHandle(ActivityManager.getCurrentUser()),
- null /* overrideGroupKey */,
- 0 /* postTime */);
- }
-
- protected StatusBarNotification createSbn(int id, Notification.Builder n) {
- return createSbn(id, n.build());
- }
-
- protected StatusBarNotification createSbn(int id) {
- final Notification.Builder b = new Notification.Builder(mContext, "")
- .setSmallIcon(R.drawable.ic_person)
- .setContentTitle("Title")
- .setContentText("Text");
- return createSbn(id, b);
- }
-
- protected NotificationEntry createEntry(int id, Notification n) {
- return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
- }
-
- protected NotificationEntry createEntry(int id) {
- return new NotificationEntryBuilder().setSbn(createSbn(id)).build();
- }
-
-
- @Test
- public void testShowNotification_addsEntry() {
- final AlertingNotificationManager alm = createAlertingNotificationManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
-
- alm.showNotification(entry);
-
- assertTrue(alm.isAlerting(entry.getKey()));
- assertTrue(alm.hasNotifications());
- assertEquals(entry, alm.getEntry(entry.getKey()));
- }
-
- @Test
- public void testShowNotification_autoDismisses() {
- final AlertingNotificationManager alm = createAlertingNotificationManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
-
- alm.showNotification(entry);
- mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME * 3 / 2);
-
- assertFalse(alm.isAlerting(entry.getKey()));
- }
-
- @Test
- public void testRemoveNotification_removeDeferred() {
- final AlertingNotificationManager alm = createAlertingNotificationManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
-
- alm.showNotification(entry);
-
- final boolean removedImmediately = alm.removeNotification(
- entry.getKey(), /* releaseImmediately = */ false);
- assertFalse(removedImmediately);
- assertTrue(alm.isAlerting(entry.getKey()));
- }
-
- @Test
- public void testRemoveNotification_forceRemove() {
- final AlertingNotificationManager alm = createAlertingNotificationManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
-
- alm.showNotification(entry);
-
- final boolean removedImmediately = alm.removeNotification(
- entry.getKey(), /* releaseImmediately = */ true);
- assertTrue(removedImmediately);
- assertFalse(alm.isAlerting(entry.getKey()));
- }
-
- @Test
- public void testReleaseAllImmediately() {
- final AlertingNotificationManager alm = createAlertingNotificationManager();
- for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
- final NotificationEntry entry = createEntry(i);
- entry.setRow(mRow);
- alm.showNotification(entry);
- }
-
- alm.releaseAllImmediately();
-
- assertEquals(0, alm.getAllEntries().count());
- }
-
- @Test
- public void testCanRemoveImmediately_notShownLongEnough() {
- final AlertingNotificationManager alm = createAlertingNotificationManager();
- final NotificationEntry entry = createEntry(/* id = */ 0);
-
- alm.showNotification(entry);
-
- // The entry has just been added so we should not remove immediately.
- assertFalse(alm.canRemoveImmediately(entry.getKey()));
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index 4a365b9ab084..05e866e85112 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -41,10 +41,12 @@ import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions
import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.power.data.repository.FakePowerRepository
import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
import com.android.systemui.shade.LargeScreenHeaderHelper
import com.android.systemui.shade.data.repository.FakeShadeRepository
@@ -56,6 +58,7 @@ import com.android.systemui.statusbar.notification.stack.domain.interactor.Share
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
import com.android.systemui.statusbar.policy.data.repository.FakeUserSetupRepository
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
import kotlinx.coroutines.flow.emptyFlow
import org.junit.Assert.assertEquals
@@ -71,17 +74,17 @@ import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class StatusBarStateControllerImplTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
- private val testDispatcher = utils.testDispatcher
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val testDispatcher = kosmos.testDispatcher
private lateinit var shadeInteractor: ShadeInteractor
private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
private lateinit var fromPrimaryBouncerTransitionInteractor:
@@ -131,7 +134,7 @@ class StatusBarStateControllerImplTest : SysuiTestCase() {
FakeKeyguardBouncerRepository(),
ConfigurationInteractor(configurationRepository),
shadeRepository,
- utils::sceneInteractor
+ { kosmos.sceneInteractor },
)
val keyguardTransitionInteractor =
KeyguardTransitionInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
index 605936372e7a..cd744100770e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
@@ -73,7 +73,7 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() {
}
private fun flagNotificationAsHun() {
- `when`(headsUpManager.isAlerting(notificationKey)).thenReturn(true)
+ `when`(headsUpManager.isHeadsUpEntry(notificationKey)).thenReturn(true)
}
@Test
@@ -151,8 +151,8 @@ class NotificationLaunchAnimatorControllerTest : SysuiTestCase() {
.build()
assertSame(summary, notification.entry.parent?.summary)
- `when`(headsUpManager.isAlerting(notificationKey)).thenReturn(false)
- `when`(headsUpManager.isAlerting(summary.key)).thenReturn(true)
+ `when`(headsUpManager.isHeadsUpEntry(notificationKey)).thenReturn(false)
+ `when`(headsUpManager.isHeadsUpEntry(summary.key)).thenReturn(true)
assertNotSame(GROUP_ALERT_SUMMARY, summary.sbn.notification.groupAlertBehavior)
assertNotSame(GROUP_ALERT_SUMMARY, notification.entry.sbn.notification.groupAlertBehavior)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index a8be62b367b4..cd75e0811fff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -148,7 +148,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
verify(remoteInputManager).addActionPressListener(capture())
}
given(headsUpManager.allEntries).willAnswer { huns.stream() }
- given(headsUpManager.isAlerting(anyString())).willAnswer { invocation ->
+ given(headsUpManager.isHeadsUpEntry(anyString())).willAnswer { invocation ->
val key = invocation.getArgument<String>(0)
huns.any { entry -> entry.key == key }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index 2e74d119849e..ea5a6e72557a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -140,7 +140,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
.setSummary(mEntry)
.build();
- when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(false);
+ when(mHeadsUpManager.isHeadsUpEntry(mEntry.getKey())).thenReturn(false);
// Whenever we invalidate, the pipeline runs again, so we invalidate the state
doAnswer(i -> {
@@ -373,7 +373,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
setSleepy(false);
// WHEN a notification is alerting and visible
- when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+ when(mHeadsUpManager.isHeadsUpEntry(mEntry.getKey())).thenReturn(true);
when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
.thenReturn(true);
@@ -389,7 +389,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
setSleepy(false);
// WHEN a notification is alerting but not visible
- when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+ when(mHeadsUpManager.isHeadsUpEntry(mEntry.getKey())).thenReturn(true);
when(mVisibilityLocationProvider.isInVisibleLocation(any(NotificationEntry.class)))
.thenReturn(false);
@@ -537,7 +537,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
// GIVEN mEntry is a HUN
- when(mHeadsUpManager.isAlerting(mEntry.getKey())).thenReturn(true);
+ when(mHeadsUpManager.isHeadsUpEntry(mEntry.getKey())).thenReturn(true);
// THEN group + section changes are allowed
assertTrue(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index 168e782d0481..ff02ef3d4e62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -28,6 +28,8 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
+
import android.app.Notification;
import android.os.Handler;
import android.os.Looper;
@@ -56,6 +58,8 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.logging.nano.Notifications;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -110,6 +114,11 @@ public class NotificationLoggerTest extends SysuiTestCase {
private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
private final PowerInteractor mPowerInteractor =
PowerInteractorFactory.create().getPowerInteractor();
+ private final ActiveNotificationListRepository mActiveNotificationListRepository =
+ new ActiveNotificationListRepository();
+ private final ActiveNotificationsInteractor mActiveNotificationsInteractor =
+ new ActiveNotificationsInteractor(mActiveNotificationListRepository,
+ StandardTestDispatcher(null, null));
private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
private final JavaAdapter mJavaAdapter = new JavaAdapter(mTestScope.getBackgroundScope());
@@ -123,7 +132,8 @@ public class NotificationLoggerTest extends SysuiTestCase {
new WindowRootViewVisibilityRepository(mBarService, mUiBgExecutor),
mKeyguardRepository,
mHeadsUpManager,
- mPowerInteractor);
+ mPowerInteractor,
+ mActiveNotificationsInteractor);
mWindowRootViewVisibilityInteractor.setIsLockscreenOrShadeVisible(true);
mEntry = new NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 8a730cfd7ddd..71613edb8737 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -42,6 +42,8 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
+
import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
@@ -85,6 +87,8 @@ import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository;
+import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -156,6 +160,12 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
@Mock private UserManager mUserManager;
+ private final ActiveNotificationListRepository mActiveNotificationListRepository =
+ new ActiveNotificationListRepository();
+ private final ActiveNotificationsInteractor mActiveNotificationsInteractor =
+ new ActiveNotificationsInteractor(mActiveNotificationListRepository,
+ StandardTestDispatcher(null, null));
+
private WindowRootViewVisibilityInteractor mWindowRootViewVisibilityInteractor;
@Before
@@ -171,7 +181,8 @@ public class NotificationGutsManagerTest extends SysuiTestCase {
new WindowRootViewVisibilityRepository(mBarService, mExecutor),
new FakeKeyguardRepository(),
mHeadsUpManager,
- PowerInteractorFactory.create().getPowerInteractor());
+ PowerInteractorFactory.create().getPowerInteractor(),
+ mActiveNotificationsInteractor);
mGutsManager = new NotificationGutsManager(
mContext,
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 88662b60c7d1..89f826b2049d 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
@@ -66,6 +66,8 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlags;
+import com.android.systemui.scene.ui.view.WindowRootView;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -91,6 +93,7 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
import com.android.systemui.statusbar.notification.stack.NotificationSwipeHelper.NotificationCallback;
+import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor;
import com.android.systemui.statusbar.notification.stack.ui.viewbinder.NotificationListViewBinder;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScrimController;
@@ -112,6 +115,8 @@ import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import javax.inject.Provider;
+
/**
* Tests for {@link NotificationStackScrollLayoutController}.
*/
@@ -153,6 +158,9 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private NotificationRemoteInputManager mRemoteInputManager;
@Mock private VisibilityLocationProviderDelegator mVisibilityLocationProviderDelegator;
@Mock private ShadeController mShadeController;
+ @Mock private SceneContainerFlags mSceneContainerFlags;
+ @Mock private Provider<WindowRootView> mWindowRootView;
+ @Mock private NotificationStackAppearanceInteractor mNotificationStackAppearanceInteractor;
@Mock private InteractionJankMonitor mJankMonitor;
private final StackStateLogger mStackLogger = new StackStateLogger(logcatLogBuffer(),
logcatLogBuffer());
@@ -724,6 +732,9 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mSeenNotificationsInteractor,
mViewBinder,
mShadeController,
+ mSceneContainerFlags,
+ mWindowRootView,
+ mNotificationStackAppearanceInteractor,
mJankMonitor,
mStackLogger,
mLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index a1721208b2f2..4afcc8c9da43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -20,6 +20,7 @@ import static android.view.View.GONE;
import static android.view.WindowInsets.Type.ime;
import static com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION;
+import static com.android.systemui.Flags.FLAG_SCENE_CONTAINER;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL;
@@ -37,6 +38,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
@@ -51,6 +53,7 @@ import static org.mockito.Mockito.when;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.os.SystemClock;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.testing.AndroidTestingRunner;
@@ -74,6 +77,7 @@ import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
import com.android.systemui.shade.ShadeController;
import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
import com.android.systemui.statusbar.EmptyShadeView;
@@ -93,11 +97,13 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
import org.junit.Assert;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -947,6 +953,78 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
verify(runnable).run();
}
+ @Test
+ public void testDispatchTouchEvent_sceneContainerDisabled() {
+ Assume.assumeFalse(SceneContainerFlag.isEnabled());
+
+ MotionEvent event = MotionEvent.obtain(
+ SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_MOVE,
+ 0,
+ 0,
+ 0
+ );
+
+ mStackScroller.dispatchTouchEvent(event);
+
+ verify(mStackScrollLayoutController, never()).sendTouchToSceneFramework(any());
+ }
+
+ @Test
+ public void testDispatchTouchEvent_sceneContainerEnabled() {
+ Assume.assumeTrue(SceneContainerFlag.isEnabled());
+ mStackScroller.setIsBeingDragged(true);
+
+ MotionEvent moveEvent = MotionEvent.obtain(
+ SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_MOVE,
+ 0,
+ 0,
+ 0
+ );
+ MotionEvent syntheticDownEvent = moveEvent.copy();
+ syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN);
+ mStackScroller.dispatchTouchEvent(moveEvent);
+
+ verify(mStackScrollLayoutController).sendTouchToSceneFramework(argThat(
+ new MotionEventMatcher(syntheticDownEvent)));
+
+ mStackScroller.dispatchTouchEvent(moveEvent);
+
+ verify(mStackScrollLayoutController).sendTouchToSceneFramework(moveEvent);
+ }
+
+ @Test
+ @EnableFlags(FLAG_SCENE_CONTAINER)
+ public void testDispatchTouchEvent_sceneContainerEnabled_actionUp() {
+ Assume.assumeTrue(SceneContainerFlag.isEnabled());
+ mStackScroller.setIsBeingDragged(true);
+
+ MotionEvent upEvent = MotionEvent.obtain(
+ SystemClock.uptimeMillis(),
+ SystemClock.uptimeMillis(),
+ MotionEvent.ACTION_UP,
+ 0,
+ 0,
+ 0
+ );
+ MotionEvent syntheticDownEvent = upEvent.copy();
+ syntheticDownEvent.setAction(MotionEvent.ACTION_DOWN);
+
+ mStackScroller.dispatchTouchEvent(upEvent);
+
+ verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat(
+ new MotionEventMatcher(syntheticDownEvent)));
+
+ mStackScroller.dispatchTouchEvent(upEvent);
+
+ verify(mStackScrollLayoutController, atLeastOnce()).sendTouchToSceneFramework(argThat(
+ new MotionEventMatcher(upEvent)));
+ assertFalse(mStackScroller.getIsBeingDragged());
+ }
+
private void setBarStateForTest(int state) {
// Can't inject this through the listener or we end up on the actual implementation
// rather than the mock because the spy just coppied the anonymous inner /shruggie.
@@ -993,4 +1071,21 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
/* metaState= */0
);
}
+
+ private static class MotionEventMatcher implements ArgumentMatcher<MotionEvent> {
+ private final MotionEvent mLeftEvent;
+
+ MotionEventMatcher(MotionEvent leftEvent) {
+ mLeftEvent = leftEvent;
+ }
+
+ @Override
+ public boolean matches(MotionEvent right) {
+ return mLeftEvent.getActionMasked() == right.getActionMasked()
+ && mLeftEvent.getDownTime() == right.getDownTime()
+ && mLeftEvent.getEventTime() == right.getEventTime()
+ && mLeftEvent.getX() == right.getX()
+ && mLeftEvent.getY() == right.getY();
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 9f15b05f8f02..a824bc4f803f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -484,6 +484,46 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() {
}
@Test
+ fun translationYUpdatesOnKeyguard() =
+ testScope.runTest {
+ val translationY by collectLastValue(underTest.translationY)
+
+ configurationRepository.setDimensionPixelSize(
+ R.dimen.keyguard_translate_distance_on_swipe_up,
+ -100
+ )
+ configurationRepository.onAnyConfigurationChange()
+
+ // legacy expansion means the user is swiping up, usually for the bouncer
+ shadeRepository.setLegacyShadeExpansion(0.5f)
+
+ showLockscreen()
+
+ // The translation values are negative
+ assertThat(translationY).isLessThan(0f)
+ }
+
+ @Test
+ fun translationYDoesNotUpdateWhenShadeIsExpanded() =
+ testScope.runTest {
+ val translationY by collectLastValue(underTest.translationY)
+
+ configurationRepository.setDimensionPixelSize(
+ R.dimen.keyguard_translate_distance_on_swipe_up,
+ -100
+ )
+ configurationRepository.onAnyConfigurationChange()
+
+ // legacy expansion means the user is swiping up, usually for the bouncer but also for
+ // shade collapsing
+ shadeRepository.setLegacyShadeExpansion(0.5f)
+
+ showLockscreenWithShadeExpanded()
+
+ assertThat(translationY).isEqualTo(0f)
+ }
+
+ @Test
fun updateBounds_fromKeyguardRoot() =
testScope.runTest {
val bounds by collectLastValue(underTest.bounds)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 2b1f5fca8feb..c350de21dda5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -35,13 +35,12 @@ import com.android.internal.logging.UiEventLogger;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.res.R;
import com.android.systemui.shade.domain.interactor.ShadeInteractor;
-import com.android.systemui.statusbar.AlertingNotificationManager;
-import com.android.systemui.statusbar.AlertingNotificationManagerTest;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
+import com.android.systemui.statusbar.policy.BaseHeadsUpManagerTest;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.HeadsUpManagerLogger;
@@ -64,7 +63,7 @@ import kotlinx.coroutines.flow.StateFlowKt;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest {
+public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest {
@Rule public MockitoRule rule = MockitoJUnit.rule();
private final HeadsUpManagerLogger mHeadsUpManagerLogger = new HeadsUpManagerLogger(
@@ -137,11 +136,6 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest {
);
}
- @Override
- protected AlertingNotificationManager createAlertingNotificationManager() {
- return createHeadsUpManagerPhone();
- }
-
@Before
public void setUp() {
when(mShadeInteractor.isAnyExpanded()).thenReturn(StateFlowKt.MutableStateFlow(false));
@@ -179,7 +173,7 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest {
/* releaseImmediately = */ false);
assertTrue(removedImmediately);
- assertFalse(hmp.isAlerting(entry.getKey()));
+ assertFalse(hmp.isHeadsUpEntry(entry.getKey()));
}
@Test
@@ -218,6 +212,6 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest {
hmp.extendHeadsUp();
mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2);
- assertTrue(hmp.isAlerting(entry.getKey()));
+ assertTrue(hmp.isHeadsUpEntry(entry.getKey()));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
index 91cbc3274900..7362e342f321 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBypassControllerTest.kt
@@ -24,9 +24,9 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.policy.DevicePostureController
@@ -35,6 +35,7 @@ import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POST
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED
import com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.testKosmos
import com.android.systemui.tuner.TunerService
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -60,8 +61,8 @@ import org.mockito.junit.MockitoJUnit
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class KeyguardBypassControllerTest : SysuiTestCase() {
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private val featureFlags = FakeFeatureFlags()
private val shadeRepository = FakeShadeRepository()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 5fa7f13ecd6e..2d120cd9b13f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -60,10 +60,10 @@ import com.android.systemui.flags.FakeFeatureFlagsClassic;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractorFactory;
import com.android.systemui.res.R;
-import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.shade.ShadeViewStateProvider;
import com.android.systemui.shade.data.repository.FakeShadeRepository;
import com.android.systemui.statusbar.CommandQueue;
@@ -149,7 +149,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
private final TestScope mTestScope = TestScopeProvider.getTestScope();
private final FakeKeyguardRepository mKeyguardRepository = new FakeKeyguardRepository();
- private final SceneTestUtils mSceneTestUtils = new SceneTestUtils(this);
+ private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
private KeyguardInteractor mKeyguardInteractor;
private KeyguardStatusBarViewModel mViewModel;
@@ -166,11 +166,11 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
mKeyguardRepository,
mCommandQueue,
PowerInteractorFactory.create().getPowerInteractor(),
- mSceneTestUtils.getFakeSceneContainerFlags(),
+ mKosmos.getFakeSceneContainerFlags(),
new FakeKeyguardBouncerRepository(),
new ConfigurationInteractor(new FakeConfigurationRepository()),
new FakeShadeRepository(),
- () -> mSceneTestUtils.sceneInteractor());
+ () -> mKosmos.getSceneInteractor());
mViewModel =
new KeyguardStatusBarViewModel(
mTestScope.getBackgroundScope(),
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 7d91e8baa92c..02e6fd5a9d6e 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
@@ -35,7 +35,6 @@ import android.telephony.satellite.SatelliteModemStateCallback
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.MIN_UPTIME
import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.POLLING_INTERVAL_MS
import com.android.systemui.statusbar.pipeline.satellite.shared.model.SatelliteConnectionState
@@ -88,7 +87,6 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
Optional.empty(),
dispatcher,
testScope.backgroundScope,
- FakeLogBuffer.Factory.create(),
systemClock,
)
@@ -102,22 +100,6 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
}
@Test
- fun satelliteManagerThrows_doesNotCrash() =
- testScope.runTest {
- setupDefaultRepo()
-
- whenever(satelliteManager.registerForNtnSignalStrengthChanged(any(), any()))
- .thenThrow(SatelliteException(13))
-
- val conn by collectLastValue(underTest.connectionState)
- val strength by collectLastValue(underTest.signalStrength)
-
- // Flows have not emitted, we haven't crashed
- assertThat(conn).isNull()
- assertThat(strength).isNull()
- }
-
- @Test
fun connectionState_mapsFromSatelliteModemState() =
testScope.runTest {
setupDefaultRepo()
@@ -398,7 +380,6 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() {
if (satMan != null) Optional.of(satMan) else Optional.empty(),
dispatcher,
testScope.backgroundScope,
- FakeLogBuffer.Factory.create(),
systemClock,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
deleted file mode 100644
index 21c038ad476d..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/ui/viewmodel/DeviceBasedSatelliteViewModelTest.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.satellite.ui.viewmodel
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
-import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
-import com.android.systemui.statusbar.pipeline.satellite.data.prod.FakeDeviceBasedSatelliteRepository
-import com.android.systemui.statusbar.pipeline.satellite.domain.interactor.DeviceBasedSatelliteInteractor
-import com.android.systemui.util.mockito.mock
-import com.google.common.truth.Truth.assertThat
-import kotlin.test.Test
-import kotlinx.coroutines.test.TestScope
-import kotlinx.coroutines.test.runTest
-import org.junit.Before
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-class DeviceBasedSatelliteViewModelTest : SysuiTestCase() {
- private lateinit var underTest: DeviceBasedSatelliteViewModel
- private lateinit var interactor: DeviceBasedSatelliteInteractor
-
- private val repo = FakeDeviceBasedSatelliteRepository()
- private val mobileIconsInteractor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
-
- private val testScope = TestScope()
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- interactor =
- DeviceBasedSatelliteInteractor(
- repo,
- mobileIconsInteractor,
- testScope.backgroundScope,
- )
-
- underTest =
- DeviceBasedSatelliteViewModel(
- interactor,
- testScope.backgroundScope,
- )
- }
-
- @Test
- fun icon_nullWhenShouldNotShow_satelliteNotAllowed() =
- testScope.runTest {
- val latest by collectLastValue(underTest.icon)
-
- // GIVEN satellite is not allowed
- repo.isSatelliteAllowedForCurrentLocation.value = false
-
- // GIVEN all icons are OOS
- val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
- i1.isInService.value = false
-
- // THEN icon is null because we should not be showing it
- assertThat(latest).isNull()
- }
-
- @Test
- fun icon_nullWhenShouldNotShow_notAllOos() =
- testScope.runTest {
- val latest by collectLastValue(underTest.icon)
-
- // GIVEN satellite is allowed
- repo.isSatelliteAllowedForCurrentLocation.value = true
-
- // GIVEN all icons are not OOS
- val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
- i1.isInService.value = true
-
- // THEN icon is null because we have service
- assertThat(latest).isNull()
- }
-
- @Test
- fun icon_satelliteIsOff() =
- testScope.runTest {
- val latest by collectLastValue(underTest.icon)
-
- // GIVEN satellite is allowed
- repo.isSatelliteAllowedForCurrentLocation.value = true
-
- // GIVEN all icons are OOS
- val i1 = mobileIconsInteractor.getMobileConnectionInteractorForSubId(1)
- i1.isInService.value = false
-
- // THEN icon is null because we have service
- assertThat(latest).isInstanceOf(Icon::class.java)
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt
deleted file mode 100644
index ca9df57e8798..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/view/SingleBindableStatusBarIconViewTest.kt
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.pipeline.shared.ui.view
-
-import android.graphics.Rect
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.StatusBarIconView
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-/**
- * Being a simple subclass of [ModernStatusBarView], use the same basic test cases to verify the
- * root behavior, and add testing for the new [SingleBindableStatusBarIconView.withDefaultBinding]
- * method.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class SingleBindableStatusBarIconViewTest : SysuiTestCase() {
- private lateinit var binding: SingleBindableStatusBarIconViewBinding
-
- // Visibility is outsourced to view-models. This simulates it
- private var isVisible = true
- private var visibilityFn: () -> Boolean = { isVisible }
-
- @Test
- fun initView_hasCorrectSlot() {
- val view = createAndInitView()
-
- assertThat(view.slot).isEqualTo(SLOT_NAME)
- }
-
- @Test
- fun getVisibleState_icon_returnsIcon() {
- val view = createAndInitView()
-
- view.setVisibleState(StatusBarIconView.STATE_ICON, /* animate= */ false)
-
- assertThat(view.visibleState).isEqualTo(StatusBarIconView.STATE_ICON)
- }
-
- @Test
- fun getVisibleState_dot_returnsDot() {
- val view = createAndInitView()
-
- view.setVisibleState(StatusBarIconView.STATE_DOT, /* animate= */ false)
-
- assertThat(view.visibleState).isEqualTo(StatusBarIconView.STATE_DOT)
- }
-
- @Test
- fun getVisibleState_hidden_returnsHidden() {
- val view = createAndInitView()
-
- view.setVisibleState(StatusBarIconView.STATE_HIDDEN, /* animate= */ false)
-
- assertThat(view.visibleState).isEqualTo(StatusBarIconView.STATE_HIDDEN)
- }
-
- @Test
- fun onDarkChanged_bindingReceivesIconAndDecorTint() {
- val view = createAndInitView()
-
- view.onDarkChangedWithContrast(arrayListOf(), 0x12345678, 0x12344321)
-
- assertThat(binding.iconTint).isEqualTo(0x12345678)
- assertThat(binding.decorTint).isEqualTo(0x12345678)
- }
-
- @Test
- fun setStaticDrawableColor_bindingReceivesIconTint() {
- val view = createAndInitView()
-
- view.setStaticDrawableColor(0x12345678, 0x12344321)
-
- assertThat(binding.iconTint).isEqualTo(0x12345678)
- }
-
- @Test
- fun setDecorColor_bindingReceivesDecorColor() {
- val view = createAndInitView()
-
- view.setDecorColor(0x23456789)
-
- assertThat(binding.decorTint).isEqualTo(0x23456789)
- }
-
- @Test
- fun isIconVisible_usesBinding_true() {
- val view = createAndInitView()
-
- isVisible = true
-
- assertThat(view.isIconVisible).isEqualTo(true)
- }
-
- @Test
- fun isIconVisible_usesBinding_false() {
- val view = createAndInitView()
-
- isVisible = false
-
- assertThat(view.isIconVisible).isEqualTo(false)
- }
-
- @Test
- fun getDrawingRect_takesTranslationIntoAccount() {
- val view = createAndInitView()
-
- view.translationX = 50f
- view.translationY = 60f
-
- val drawingRect = Rect()
- view.getDrawingRect(drawingRect)
-
- assertThat(drawingRect.left).isEqualTo(view.left + 50)
- assertThat(drawingRect.right).isEqualTo(view.right + 50)
- assertThat(drawingRect.top).isEqualTo(view.top + 60)
- assertThat(drawingRect.bottom).isEqualTo(view.bottom + 60)
- }
-
- private fun createAndInitView(): SingleBindableStatusBarIconView {
- val view = SingleBindableStatusBarIconView.createView(context)
- binding = SingleBindableStatusBarIconView.withDefaultBinding(view, visibilityFn) {}
- view.initView(SLOT_NAME) { binding }
- return view
- }
-
- companion object {
- private const val SLOT_NAME = "test_slot"
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index 6a3b2c3ea55c..4c824c0d130a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.policy;
import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.util.concurrency.MockExecutorHandlerKt.mockExecutorHandler;
import static com.google.common.truth.Truth.assertThat;
@@ -36,12 +37,15 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Person;
import android.content.Context;
import android.content.Intent;
import android.graphics.Region;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -51,13 +55,16 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.systemui.SysuiTestCase;
import com.android.systemui.res.R;
-import com.android.systemui.statusbar.AlertingNotificationManager;
-import com.android.systemui.statusbar.AlertingNotificationManagerTest;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.settings.FakeGlobalSettings;
import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.util.time.SystemClock;
import org.junit.Rule;
@@ -70,10 +77,12 @@ import org.mockito.junit.MockitoRule;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
+public class BaseHeadsUpManagerTest extends SysuiTestCase {
@Rule
public MockitoRule rule = MockitoJUnit.rule();
+ private static final String TEST_PACKAGE_NAME = "BaseHeadsUpManagerTest";
+
private static final int TEST_TOUCH_ACCEPTANCE_TIME = 200;
private static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
@@ -81,6 +90,20 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
@Mock private AccessibilityManagerWrapper mAccessibilityMgr;
+ private static final int TEST_UID = 0;
+
+ protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
+ protected static final int TEST_AUTO_DISMISS_TIME = 600;
+ protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
+ // Number of notifications to use in tests requiring multiple notifications
+ private static final int TEST_NUM_NOTIFICATIONS = 4;
+
+ protected final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings();
+ protected final FakeSystemClock mSystemClock = new FakeSystemClock();
+ protected final FakeExecutor mExecutor = new FakeExecutor(mSystemClock);
+
+ @Mock protected ExpandableNotificationRow mRow;
+
static {
assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
@@ -88,6 +111,9 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
}
private final class TestableHeadsUpManager extends BaseHeadsUpManager {
+
+ private HeadsUpEntry mLastCreatedEntry;
+
TestableHeadsUpManager(Context context,
HeadsUpManagerLogger logger,
DelayableExecutor executor,
@@ -97,10 +123,23 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
UiEventLogger uiEventLogger) {
super(context, logger, mockExecutorHandler(executor), globalSettings, systemClock,
executor, accessibilityManagerWrapper, uiEventLogger);
+
mTouchAcceptanceDelay = TEST_TOUCH_ACCEPTANCE_TIME;
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
mStickyForSomeTimeAutoDismissTime = TEST_STICKY_AUTO_DISMISS_TIME;
+
+ }
+
+ @Override
+ protected HeadsUpEntry createHeadsUpEntry() {
+ mLastCreatedEntry = spy(super.createHeadsUpEntry());
+ return mLastCreatedEntry;
+ }
+
+ @Override
+ public int getContentFlag() {
+ return FLAG_CONTENT_VIEW_CONTRACTED;
}
// The following are only implemented by HeadsUpManagerPhone. If you need them, use that.
@@ -173,16 +212,46 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
}
}
+ protected StatusBarNotification createSbn(int id, Notification n) {
+ return new StatusBarNotification(
+ TEST_PACKAGE_NAME /* pkg */,
+ TEST_PACKAGE_NAME,
+ id,
+ null /* tag */,
+ TEST_UID,
+ 0 /* initialPid */,
+ n,
+ new UserHandle(ActivityManager.getCurrentUser()),
+ null /* overrideGroupKey */,
+ 0 /* postTime */);
+ }
+
+ protected StatusBarNotification createSbn(int id, Notification.Builder n) {
+ return createSbn(id, n.build());
+ }
+
+ protected StatusBarNotification createSbn(int id) {
+ final Notification.Builder b = new Notification.Builder(mContext, "")
+ .setSmallIcon(R.drawable.ic_person)
+ .setContentTitle("Title")
+ .setContentText("Text");
+ return createSbn(id, b);
+ }
+
+ protected NotificationEntry createEntry(int id, Notification n) {
+ return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
+ }
+
+ protected NotificationEntry createEntry(int id) {
+ return new NotificationEntryBuilder().setSbn(createSbn(id)).build();
+ }
+
+
private BaseHeadsUpManager createHeadsUpManager() {
return new TestableHeadsUpManager(mContext, mLogger, mExecutor, mGlobalSettings,
mSystemClock, mAccessibilityMgr, mUiEventLoggerFake);
}
- @Override
- protected AlertingNotificationManager createAlertingNotificationManager() {
- return createHeadsUpManager();
- }
-
private NotificationEntry createStickyEntry(int id) {
final Notification notif = new Notification.Builder(mContext, "")
.setSmallIcon(R.drawable.ic_person)
@@ -224,6 +293,79 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
}
}
+ @Test
+ public void testShowNotification_addsEntry() {
+ final BaseHeadsUpManager alm = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ alm.showNotification(entry);
+
+ assertTrue(alm.isHeadsUpEntry(entry.getKey()));
+ assertTrue(alm.hasNotifications());
+ assertEquals(entry, alm.getEntry(entry.getKey()));
+ }
+
+ @Test
+ public void testShowNotification_autoDismisses() {
+ final BaseHeadsUpManager alm = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ alm.showNotification(entry);
+ mSystemClock.advanceTime(TEST_AUTO_DISMISS_TIME * 3 / 2);
+
+ assertFalse(alm.isHeadsUpEntry(entry.getKey()));
+ }
+
+ @Test
+ public void testRemoveNotification_removeDeferred() {
+ final BaseHeadsUpManager alm = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ alm.showNotification(entry);
+
+ final boolean removedImmediately = alm.removeNotification(
+ entry.getKey(), /* releaseImmediately = */ false);
+ assertFalse(removedImmediately);
+ assertTrue(alm.isHeadsUpEntry(entry.getKey()));
+ }
+
+ @Test
+ public void testRemoveNotification_forceRemove() {
+ final BaseHeadsUpManager alm = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ alm.showNotification(entry);
+
+ final boolean removedImmediately = alm.removeNotification(
+ entry.getKey(), /* releaseImmediately = */ true);
+ assertTrue(removedImmediately);
+ assertFalse(alm.isHeadsUpEntry(entry.getKey()));
+ }
+
+ @Test
+ public void testReleaseAllImmediately() {
+ final BaseHeadsUpManager alm = createHeadsUpManager();
+ for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
+ final NotificationEntry entry = createEntry(i);
+ entry.setRow(mRow);
+ alm.showNotification(entry);
+ }
+
+ alm.releaseAllImmediately();
+
+ assertEquals(0, alm.getAllEntries().count());
+ }
+
+ @Test
+ public void testCanRemoveImmediately_notShownLongEnough() {
+ final BaseHeadsUpManager alm = createHeadsUpManager();
+ final NotificationEntry entry = createEntry(/* id = */ 0);
+
+ alm.showNotification(entry);
+
+ // The entry has just been added so we should not remove immediately.
+ assertFalse(alm.canRemoveImmediately(entry.getKey()));
+ }
@Test
public void testHunRemovedLogging() {
@@ -233,7 +375,7 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
BaseHeadsUpManager.HeadsUpEntry.class);
headsUpEntry.mEntry = notifEntry;
- hum.onAlertEntryRemoved(headsUpEntry);
+ hum.onEntryRemoved(headsUpEntry);
verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(notifEntry));
}
@@ -286,7 +428,7 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
hum.showNotification(entry);
mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME);
- assertTrue(hum.isAlerting(entry.getKey()));
+ assertTrue(hum.isHeadsUpEntry(entry.getKey()));
}
@@ -300,7 +442,7 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
+ (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
- assertFalse(hum.isAlerting(entry.getKey()));
+ assertFalse(hum.isHeadsUpEntry(entry.getKey()));
}
@@ -314,7 +456,7 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
+ (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2);
- assertTrue(hum.isAlerting(entry.getKey()));
+ assertTrue(hum.isHeadsUpEntry(entry.getKey()));
}
@@ -327,7 +469,7 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
hum.showNotification(entry);
mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME);
- assertTrue(hum.isAlerting(entry.getKey()));
+ assertTrue(hum.isHeadsUpEntry(entry.getKey()));
}
@@ -341,7 +483,7 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
+ (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
- assertTrue(hum.isAlerting(entry.getKey()));
+ assertTrue(hum.isHeadsUpEntry(entry.getKey()));
}
@@ -355,7 +497,7 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
mSystemClock.advanceTime(TEST_TOUCH_ACCEPTANCE_TIME
+ (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2);
- assertTrue(hum.isAlerting(entry.getKey()));
+ assertTrue(hum.isHeadsUpEntry(entry.getKey()));
}
@@ -370,11 +512,11 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
final boolean removedImmediately = hum.removeNotification(
entry.getKey(), /* releaseImmediately = */ false);
assertFalse(removedImmediately);
- assertTrue(hum.isAlerting(entry.getKey()));
+ assertTrue(hum.isHeadsUpEntry(entry.getKey()));
mSystemClock.advanceTime((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2);
- assertFalse(hum.isAlerting(entry.getKey()));
+ assertFalse(hum.isHeadsUpEntry(entry.getKey()));
}
@@ -387,12 +529,12 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
hum.showNotification(entry);
mSystemClock.advanceTime((TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2);
- assertTrue(hum.isAlerting(entry.getKey()));
+ assertTrue(hum.isHeadsUpEntry(entry.getKey()));
final boolean removedImmediately = hum.removeNotification(
entry.getKey(), /* releaseImmediately = */ false);
assertTrue(removedImmediately);
- assertFalse(hum.isAlerting(entry.getKey()));
+ assertFalse(hum.isHeadsUpEntry(entry.getKey()));
}
@@ -406,7 +548,7 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
final boolean removedImmediately = hum.removeNotification(
entry.getKey(), /* releaseImmediately = */ true);
assertTrue(removedImmediately);
- assertFalse(hum.isAlerting(entry.getKey()));
+ assertFalse(hum.isHeadsUpEntry(entry.getKey()));
}
@@ -560,7 +702,7 @@ public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest {
// the notification and then updates it; in order to not log twice, the entry needs
// to have a functional ExpandableNotificationRow that can keep track of whether it's
// pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
- hum.onAlertEntryAdded(entryToPin);
+ hum.onEntryAdded(entryToPin);
assertEquals(1, mUiEventLoggerFake.numLogs());
assertEquals(BaseHeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
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 5f9c096b1f3c..ca0e526bbc30 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
@@ -25,20 +25,22 @@ import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
-import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
import com.android.systemui.shade.data.repository.FakeShadeRepository
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.data.repository.FakeKeyguardStatusBarRepository
import com.android.systemui.statusbar.domain.interactor.KeyguardStatusBarInteractor
import com.android.systemui.statusbar.policy.BatteryController
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn
-import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Test
@@ -47,20 +49,20 @@ import org.mockito.Mockito.verify
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
class KeyguardStatusBarViewModelTest : SysuiTestCase() {
- private val testScope = TestScope()
- private val sceneTestUtils = SceneTestUtils(this)
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private val keyguardRepository = FakeKeyguardRepository()
private val keyguardInteractor =
KeyguardInteractor(
keyguardRepository,
mock<CommandQueue>(),
PowerInteractorFactory.create().powerInteractor,
- sceneTestUtils.fakeSceneContainerFlags,
+ kosmos.fakeSceneContainerFlags,
FakeKeyguardBouncerRepository(),
ConfigurationInteractor(FakeConfigurationRepository()),
FakeShadeRepository(),
) {
- sceneTestUtils.sceneInteractor()
+ kosmos.sceneInteractor
}
private val keyguardStatusBarInteractor =
KeyguardStatusBarInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
index 5b4f4d37214f..95c934e988e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProviderTest.kt
@@ -88,6 +88,30 @@ class ScopedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
}
@Test
+ fun setReadyToHandleTransition_whileTransitionRunning_fromBgThread_propagatesCallbacks() =
+ testScope.runTest {
+ runBlockingInBg { rootProvider.onTransitionStarted() }
+
+ runBlockingInBg {
+ // This causes the transition started callback to be propagated immediately, without
+ // the need to switch thread (as we're already in the correct one). We don't need a
+ // sync barrier on the bg thread as in
+ // setReadyToHandleTransition_whileTransitionRunning_propagatesCallbacks here.
+ scopedProvider.setReadyToHandleTransition(true)
+ }
+
+ listener.assertStarted()
+
+ runBlockingInBg { rootProvider.onTransitionProgress(1f) }
+
+ listener.assertLastProgress(1f)
+
+ runBlockingInBg { rootProvider.onTransitionFinished() }
+
+ listener.assertNotStarted()
+ }
+
+ @Test
fun setReadyToHandleTransition_beforeAnyCallback_doesNotCrash() {
testScope.runTest { scopedProvider.setReadyToHandleTransition(true) }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
index e6b9d9b09246..b6a033a7c5f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserSwitcherInteractorTest.kt
@@ -40,13 +40,18 @@ import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Text
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.Flags
+import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.res.R
-import com.android.systemui.scene.SceneTestUtils
import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.telephonyInteractor
+import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.UserSwitcherSettingsModel
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.source.UserRecord
@@ -98,8 +103,8 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
@Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
- private val utils = SceneTestUtils(this)
- private val testScope = utils.testScope
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var spyContext: Context
private lateinit var userRepository: FakeUserRepository
private lateinit var keyguardReply: KeyguardInteractorFactory.WithDependencies
@@ -121,16 +126,17 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
SUPERVISED_USER_CREATION_APP_PACKAGE,
)
- utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, false)
mSetFlagsRule.enableFlags(AConfigFlags.FLAG_SWITCH_USER_ON_BG)
spyContext = spy(context)
- keyguardReply = KeyguardInteractorFactory.create(featureFlags = utils.fakeFeatureFlags)
+ keyguardReply =
+ KeyguardInteractorFactory.create(featureFlags = kosmos.fakeFeatureFlagsClassic)
keyguardRepository = keyguardReply.repository
userRepository = FakeUserRepository()
refreshUsersScheduler =
RefreshUsersScheduler(
applicationScope = testScope.backgroundScope,
- mainDispatcher = utils.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
repository = userRepository,
)
}
@@ -363,7 +369,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
fun actions_deviceUnlocked_fullScreen() {
createUserInteractor()
testScope.runTest {
- utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
@@ -447,7 +453,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
fun actions_deviceLockedAddFromLockscreenSet_fullList_fullScreen() {
createUserInteractor()
testScope.runTest {
- utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val userInfos = createUserInfos(count = 2, includeGuest = false)
userRepository.setUserInfos(userInfos)
userRepository.setSelectedUserInfo(userInfos[0])
@@ -640,7 +646,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
val refreshUsersCallCount = userRepository.refreshUsersCallCount
- utils.telephonyRepository.setCallState(1)
+ kosmos.fakeTelephonyRepository.setCallState(1)
runCurrent()
assertThat(userRepository.refreshUsersCallCount).isEqualTo(refreshUsersCallCount + 1)
@@ -792,7 +798,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
fun userRecordsFullScreen() {
createUserInteractor()
testScope.runTest {
- utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val userInfos = createUserInfos(count = 3, includeGuest = false)
userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
userRepository.setUserInfos(userInfos)
@@ -901,7 +907,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
fun showUserSwitcher_fullScreenEnabled_launchesFullScreenDialog() {
createUserInteractor()
testScope.runTest {
- utils.fakeFeatureFlags.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
+ kosmos.fakeFeatureFlagsClassic.set(Flags.FULL_SCREEN_USER_SWITCHER, true)
val expandable = mock<Expandable>()
underTest.showUserSwitcher(expandable)
@@ -1116,19 +1122,19 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
manager = manager,
headlessSystemUserMode = headlessSystemUserMode,
applicationScope = testScope.backgroundScope,
- telephonyInteractor = utils.telephonyInteractor(),
+ telephonyInteractor = kosmos.telephonyInteractor,
broadcastDispatcher = fakeBroadcastDispatcher,
keyguardUpdateMonitor = keyguardUpdateMonitor,
- backgroundDispatcher = utils.testDispatcher,
- mainDispatcher = utils.testDispatcher,
+ backgroundDispatcher = kosmos.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
activityManager = activityManager,
refreshUsersScheduler = refreshUsersScheduler,
guestUserInteractor =
GuestUserInteractor(
applicationContext = spyContext,
applicationScope = testScope.backgroundScope,
- mainDispatcher = utils.testDispatcher,
- backgroundDispatcher = utils.testDispatcher,
+ mainDispatcher = kosmos.testDispatcher,
+ backgroundDispatcher = kosmos.testDispatcher,
manager = manager,
repository = userRepository,
deviceProvisionedController = deviceProvisionedController,
@@ -1139,7 +1145,7 @@ class UserSwitcherInteractorTest : SysuiTestCase() {
resetOrExitSessionReceiver = resetOrExitSessionReceiver,
),
uiEventLogger = uiEventLogger,
- featureFlags = utils.fakeFeatureFlags,
+ featureFlags = kosmos.fakeFeatureFlagsClassic,
userRestrictionChecker = mock(),
)
}
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 8920d4df2a91..1ed045ff6546 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -117,11 +117,11 @@ import com.android.systemui.keyguard.domain.interactor.GlanceableHubTransitions;
import com.android.systemui.keyguard.domain.interactor.InWindowLauncherUnlockAnimationInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.kosmos.KosmosJavaAdapter;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.domain.interactor.PowerInteractor;
import com.android.systemui.scene.FakeWindowRootViewComponent;
-import com.android.systemui.scene.SceneTestUtils;
import com.android.systemui.scene.data.repository.SceneContainerRepository;
import com.android.systemui.scene.domain.interactor.SceneInteractor;
import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
@@ -356,8 +356,8 @@ public class BubblesTest extends SysuiTestCase {
@Mock
private LargeScreenHeaderHelper mLargeScreenHeaderHelper;
- private final SceneTestUtils mUtils = new SceneTestUtils(this);
- private final TestScope mTestScope = mUtils.getTestScope();
+ private final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this);
+ private final TestScope mTestScope = mKosmos.getTestScope();
private ShadeInteractor mShadeInteractor;
private ShellTaskOrganizer mShellTaskOrganizer;
private TaskViewTransitions mTaskViewTransitions;
@@ -408,8 +408,8 @@ public class BubblesTest extends SysuiTestCase {
FakeConfigurationRepository configurationRepository = new FakeConfigurationRepository();
PowerInteractor powerInteractor = new PowerInteractor(
- mUtils.getPowerRepository(),
- mUtils.falsingCollector(),
+ mKosmos.getPowerRepository(),
+ mKosmos.getFalsingCollector(),
mock(ScreenOffAnimationController.class),
mStatusBarStateController);
@@ -417,7 +417,7 @@ public class BubblesTest extends SysuiTestCase {
mTestScope.getBackgroundScope(),
new SceneContainerRepository(
mTestScope.getBackgroundScope(),
- mUtils.fakeSceneContainerConfig()),
+ mKosmos.getFakeSceneContainerConfig()),
powerInteractor,
mock(SceneLogger.class));
@@ -449,8 +449,8 @@ public class BubblesTest extends SysuiTestCase {
keyguardTransitionRepository,
keyguardTransitionInteractor,
mTestScope.getBackgroundScope(),
- mUtils.getTestDispatcher(),
- mUtils.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
keyguardInteractor,
featureFlags,
shadeRepository,
@@ -475,8 +475,8 @@ public class BubblesTest extends SysuiTestCase {
keyguardTransitionRepository,
keyguardTransitionInteractor,
mTestScope.getBackgroundScope(),
- mUtils.getTestDispatcher(),
- mUtils.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
+ mKosmos.getTestDispatcher(),
keyguardInteractor,
featureFlags,
mock(KeyguardSecurityModel.class),
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
index 42ec8fed0127..f192de23fecc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakePromptRepository.kt
@@ -26,12 +26,24 @@ class FakePromptRepository : PromptRepository {
private val _isConfirmationRequired = MutableStateFlow(false)
override val isConfirmationRequired = _isConfirmationRequired.asStateFlow()
+ private val _opPackageName: MutableStateFlow<String?> = MutableStateFlow(null)
+ override val opPackageName = _opPackageName.asStateFlow()
+
override fun setPrompt(
promptInfo: PromptInfo,
userId: Int,
gatekeeperChallenge: Long?,
kind: PromptKind,
- ) = setPrompt(promptInfo, userId, gatekeeperChallenge, kind, forceConfirmation = false)
+ opPackageName: String,
+ ) =
+ setPrompt(
+ promptInfo,
+ userId,
+ gatekeeperChallenge,
+ kind,
+ forceConfirmation = false,
+ opPackageName = opPackageName
+ )
fun setPrompt(
promptInfo: PromptInfo,
@@ -39,12 +51,14 @@ class FakePromptRepository : PromptRepository {
gatekeeperChallenge: Long?,
kind: PromptKind,
forceConfirmation: Boolean = false,
+ opPackageName: String? = null,
) {
_promptInfo.value = promptInfo
_userId.value = userId
_challenge.value = gatekeeperChallenge
_kind.value = kind
_isConfirmationRequired.value = promptInfo.isConfirmationRequested || forceConfirmation
+ _opPackageName.value = opPackageName
}
override fun unsetPrompt() {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.kt
new file mode 100644
index 000000000000..79107cc818ba
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryKosmos.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.communal.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.communalPrefsRepository: CommunalPrefsRepository by
+ Kosmos.Fixture { fakeCommunalPrefsRepository }
+val Kosmos.fakeCommunalPrefsRepository by Kosmos.Fixture { FakeCommunalPrefsRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt
index 1f5af5c38491..482d60ce8adf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalRepositoryKosmos.kt
@@ -18,7 +18,10 @@ package com.android.systemui.communal.data.repository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
-val Kosmos.fakeCommunalRepository by Fixture { FakeCommunalRepository() }
+val Kosmos.fakeCommunalRepository by Fixture {
+ FakeCommunalRepository(applicationScope = applicationCoroutineScope)
+}
val Kosmos.communalRepository by Fixture<CommunalRepository> { fakeCommunalRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryKosmos.kt
new file mode 100644
index 000000000000..f7665fbd1136
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/CommunalTutorialRepositoryKosmos.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.communal.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.communalTutorialRepository by
+ Kosmos.Fixture<CommunalTutorialRepository> { fakeCommunalTutorialRepository }
+val Kosmos.fakeCommunalTutorialRepository by Kosmos.Fixture { FakeCommunalTutorialRepository() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.kt
new file mode 100644
index 000000000000..d3ed58bf5be0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalPrefsRepository.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.communal.data.repository
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Fake implementation of [CommunalPrefsRepository] */
+class FakeCommunalPrefsRepository : CommunalPrefsRepository {
+ private val _isCtaDismissed = MutableStateFlow(false)
+ override val isCtaDismissed: Flow<Boolean> = _isCtaDismissed.asStateFlow()
+
+ override suspend fun setCtaDismissedForCurrentUser() {
+ _isCtaDismissed.value = true
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
index e82cae45c8f0..20fa545d54e0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalRepository.kt
@@ -2,24 +2,21 @@ package com.android.systemui.communal.data.repository
import com.android.systemui.communal.shared.model.CommunalSceneKey
import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
-import com.android.systemui.dagger.qualifiers.Background
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.test.TestScope
/** Fake implementation of [CommunalRepository]. */
@OptIn(ExperimentalCoroutinesApi::class)
class FakeCommunalRepository(
- @Background applicationScope: CoroutineScope = TestScope(),
- override var isCommunalEnabled: Boolean = false,
+ applicationScope: CoroutineScope,
+ override var isCommunalEnabled: Boolean = true,
override val desiredScene: MutableStateFlow<CommunalSceneKey> =
MutableStateFlow(CommunalSceneKey.DEFAULT),
) : CommunalRepository {
@@ -53,12 +50,4 @@ class FakeCommunalRepository(
fun setIsCommunalHubShowing(isCommunalHubShowing: Boolean) {
_isCommunalHubShowing.value = isCommunalHubShowing
}
-
- private val _isCtaTileInViewModeVisible: MutableStateFlow<Boolean> = MutableStateFlow(true)
- override val isCtaTileInViewModeVisible: Flow<Boolean> =
- _isCtaTileInViewModeVisible.asStateFlow()
-
- override fun setCtaTileInViewModeVisibility(isVisible: Boolean) {
- _isCtaTileInViewModeVisible.value = isVisible
- }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
index 397dc1a464bd..d0c2d4b82fb3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/data/repository/FakeCommunalWidgetRepository.kt
@@ -35,4 +35,13 @@ class FakeCommunalWidgetRepository(private val coroutineScope: CoroutineScope) :
}
}
}
+
+ private var isHostActive = false
+ override fun updateAppWidgetHostActive(active: Boolean) {
+ isHostActive = active
+ }
+
+ fun isHostActive(): Boolean {
+ return isHostActive
+ }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
index 95ff889177b8..1753ca05347a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorFactory.kt
@@ -17,11 +17,12 @@
package com.android.systemui.communal.domain.interactor
-import android.appwidget.AppWidgetHost
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
+import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
import com.android.systemui.communal.data.repository.FakeCommunalRepository
import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
@@ -42,7 +43,8 @@ object CommunalInteractorFactory {
mediaRepository: FakeCommunalMediaRepository = FakeCommunalMediaRepository(),
smartspaceRepository: FakeSmartspaceRepository = FakeSmartspaceRepository(),
tutorialRepository: FakeCommunalTutorialRepository = FakeCommunalTutorialRepository(),
- appWidgetHost: AppWidgetHost = mock(),
+ communalPrefsRepository: FakeCommunalPrefsRepository = FakeCommunalPrefsRepository(),
+ appWidgetHost: CommunalAppWidgetHost = mock(),
editWidgetsActivityStarter: EditWidgetsActivityStarter = mock(),
): WithDependencies {
val withDeps =
@@ -55,6 +57,7 @@ object CommunalInteractorFactory {
testScope,
communalRepository,
widgetRepository,
+ communalPrefsRepository,
mediaRepository,
smartspaceRepository,
tutorialRepository,
@@ -64,8 +67,10 @@ object CommunalInteractorFactory {
appWidgetHost,
editWidgetsActivityStarter,
CommunalInteractor(
+ testScope.backgroundScope,
communalRepository,
widgetRepository,
+ communalPrefsRepository,
mediaRepository,
smartspaceRepository,
withDeps.keyguardInteractor,
@@ -79,13 +84,14 @@ object CommunalInteractorFactory {
val testScope: TestScope,
val communalRepository: FakeCommunalRepository,
val widgetRepository: FakeCommunalWidgetRepository,
+ val communalPrefsRepository: FakeCommunalPrefsRepository,
val mediaRepository: FakeCommunalMediaRepository,
val smartspaceRepository: FakeSmartspaceRepository,
val tutorialRepository: FakeCommunalTutorialRepository,
val keyguardRepository: FakeKeyguardRepository,
val keyguardInteractor: KeyguardInteractor,
val tutorialInteractor: CommunalTutorialInteractor,
- val appWidgetHost: AppWidgetHost,
+ val appWidgetHost: CommunalAppWidgetHost,
val editWidgetsActivityStarter: EditWidgetsActivityStarter,
val communalInteractor: CommunalInteractor,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
index 649b373258ef..65579a6d9ddf 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt
@@ -17,19 +17,23 @@
package com.android.systemui.communal.domain.interactor
import com.android.systemui.communal.data.repository.communalMediaRepository
+import com.android.systemui.communal.data.repository.communalPrefsRepository
import com.android.systemui.communal.data.repository.communalRepository
import com.android.systemui.communal.data.repository.communalWidgetRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.smartspace.data.repository.smartspaceRepository
import com.android.systemui.util.mockito.mock
val Kosmos.communalInteractor by Fixture {
CommunalInteractor(
+ applicationScope = applicationCoroutineScope,
communalRepository = communalRepository,
widgetRepository = communalWidgetRepository,
mediaRepository = communalMediaRepository,
+ communalPrefsRepository = communalPrefsRepository,
smartspaceRepository = smartspaceRepository,
appWidgetHost = mock(),
keyguardInteractor = keyguardInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorFactory.kt
index e5cadabfc1c7..3ff2a3ed5145 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorFactory.kt
@@ -32,7 +32,7 @@ object CommunalTutorialInteractorFactory {
communalTutorialRepository: FakeCommunalTutorialRepository =
FakeCommunalTutorialRepository(),
communalRepository: FakeCommunalRepository =
- FakeCommunalRepository(isCommunalEnabled = true),
+ FakeCommunalRepository(applicationScope = testScope.backgroundScope),
keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(),
keyguardInteractor: KeyguardInteractor =
KeyguardInteractorFactory.create(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 975db3b179ac..59f56dd18f0e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -127,6 +127,9 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
override val ambientIndicationVisible: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ private val _isEncryptedOrLockdown = MutableStateFlow(true)
+ override val isEncryptedOrLockdown: Flow<Boolean> = _isEncryptedOrLockdown
+
override fun setQuickSettingsVisible(isVisible: Boolean) {
_isQuickSettingsVisible.value = isVisible
}
@@ -247,6 +250,10 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository {
override fun setKeyguardAlpha(alpha: Float) {
_keyguardAlpha.value = alpha
}
+
+ fun setIsEncryptedOrLockdown(value: Boolean) {
+ _isEncryptedOrLockdown.value = value
+ }
}
@Module
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
index 59f0ec3cd3a5..d8f0cec54596 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/CommunalInteractorKosmos.kt
@@ -16,25 +16,32 @@
package com.android.systemui.keyguard.domain.interactor
-import android.appwidget.AppWidgetHost
import com.android.systemui.communal.data.repository.communalMediaRepository
+import com.android.systemui.communal.data.repository.communalPrefsRepository
import com.android.systemui.communal.data.repository.communalRepository
import com.android.systemui.communal.data.repository.communalWidgetRepository
import com.android.systemui.communal.domain.interactor.CommunalInteractor
+import com.android.systemui.communal.widgets.CommunalAppWidgetHost
import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
import com.android.systemui.smartspace.data.repository.smartspaceRepository
import org.mockito.Mockito.mock
val Kosmos.communalInteractor by
Kosmos.Fixture {
CommunalInteractor(
+ applicationScope = testScope.backgroundScope,
communalRepository = communalRepository,
widgetRepository = communalWidgetRepository,
mediaRepository = communalMediaRepository,
+ communalPrefsRepository = communalPrefsRepository,
smartspaceRepository = smartspaceRepository,
keyguardInteractor = keyguardInteractor,
- appWidgetHost = mock(AppWidgetHost::class.java),
- editWidgetsActivityStarter = mock(EditWidgetsActivityStarter::class.java),
+ appWidgetHost = mock(CommunalAppWidgetHost::class.java),
+ editWidgetsActivityStarter = editWidgetsActivityStarter,
)
}
+
+val Kosmos.editWidgetsActivityStarter by
+ Kosmos.Fixture<EditWidgetsActivityStarter> { mock(EditWidgetsActivityStarter::class.java) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
new file mode 100644
index 000000000000..24670b12193a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/kosmos/KosmosJavaAdapter.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.kosmos
+
+import android.content.applicationContext
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.data.repository.bouncerRepository
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
+import com.android.systemui.classifier.falsingCollector
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.communal.data.repository.fakeCommunalRepository
+import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.jank.interactionJankMonitor
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.plugins.statusbar.statusBarStateController
+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.sceneContainerConfig
+import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
+import com.android.systemui.statusbar.phone.screenOffAnimationController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
+import com.android.systemui.util.time.systemClock
+
+/** Helper for using [Kosmos] from Java. */
+@Deprecated("Please convert your test to Kotlin and use [Kosmos] directly.")
+class KosmosJavaAdapter(
+ testCase: SysuiTestCase,
+) {
+
+ private val kosmos = Kosmos()
+
+ val testDispatcher by lazy { kosmos.testDispatcher }
+ val testScope by lazy { kosmos.testScope }
+ val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic }
+ val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags }
+ val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
+ val configurationInteractor by lazy { kosmos.configurationInteractor }
+ val bouncerRepository by lazy { kosmos.bouncerRepository }
+ val communalRepository by lazy { kosmos.fakeCommunalRepository }
+ val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
+ val powerRepository by lazy { kosmos.fakePowerRepository }
+ val clock by lazy { kosmos.systemClock }
+ val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository }
+ val simBouncerInteractor by lazy { kosmos.simBouncerInteractor }
+ val statusBarStateController by lazy { kosmos.statusBarStateController }
+ val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
+ val screenOffAnimationController by lazy { kosmos.screenOffAnimationController }
+ val fakeSceneContainerConfig by lazy { kosmos.sceneContainerConfig }
+ val sceneInteractor by lazy { kosmos.sceneInteractor }
+ val falsingCollector by lazy { kosmos.falsingCollector }
+ val powerInteractor by lazy { kosmos.powerInteractor }
+
+ init {
+ kosmos.applicationContext = testCase.context
+ kosmos.testCase = testCase
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
deleted file mode 100644
index d314a25658e8..000000000000
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ /dev/null
@@ -1,186 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene
-
-import android.content.Context
-import android.content.applicationContext
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
-import com.android.systemui.authentication.domain.interactor.authenticationInteractor
-import com.android.systemui.bouncer.data.repository.bouncerRepository
-import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository
-import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
-import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
-import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel
-import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.classifier.domain.interactor.FalsingInteractor
-import com.android.systemui.classifier.domain.interactor.falsingInteractor
-import com.android.systemui.classifier.falsingCollector
-import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.configurationInteractor
-import com.android.systemui.communal.data.repository.fakeCommunalRepository
-import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.domain.interactor.communalInteractor
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
-import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
-import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.jank.interactionJankMonitor
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
-import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testCase
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.statusbar.statusBarStateController
-import com.android.systemui.power.data.repository.fakePowerRepository
-import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.power.domain.interactor.powerInteractor
-import com.android.systemui.scene.data.repository.SceneContainerRepository
-import com.android.systemui.scene.data.repository.sceneContainerRepository
-import com.android.systemui.scene.domain.interactor.SceneInteractor
-import com.android.systemui.scene.domain.interactor.sceneInteractor
-import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags
-import com.android.systemui.scene.shared.model.SceneContainerConfig
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
-import com.android.systemui.statusbar.phone.screenOffAnimationController
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
-import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
-import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
-import com.android.systemui.telephony.domain.interactor.telephonyInteractor
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.user.domain.interactor.selectedUserInteractor
-import com.android.systemui.util.time.systemClock
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-
-/**
- * Utilities for creating scene container framework related repositories, interactors, and
- * view-models for tests.
- */
-@OptIn(ExperimentalCoroutinesApi::class)
-@Deprecated("Please use Kosmos instead.")
-class SceneTestUtils {
-
- val kosmos = Kosmos()
-
- constructor(
- context: Context,
- ) {
- kosmos.applicationContext = context
- }
-
- constructor(testCase: SysuiTestCase) : this(context = testCase.context) {
- kosmos.testCase = testCase
- }
-
- val testDispatcher by lazy { kosmos.testDispatcher }
- val testScope by lazy { kosmos.testScope }
- val fakeFeatureFlags by lazy { kosmos.fakeFeatureFlagsClassic }
- val fakeSceneContainerFlags by lazy { kosmos.fakeSceneContainerFlags }
- val deviceEntryRepository by lazy { kosmos.fakeDeviceEntryRepository }
- val authenticationRepository by lazy { kosmos.fakeAuthenticationRepository }
- val configurationRepository by lazy { kosmos.fakeConfigurationRepository }
- val configurationInteractor by lazy { kosmos.configurationInteractor }
- val telephonyRepository by lazy { kosmos.fakeTelephonyRepository }
- val bouncerRepository by lazy { kosmos.bouncerRepository }
- val communalRepository by lazy { kosmos.fakeCommunalRepository }
- val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
- val powerRepository by lazy { kosmos.fakePowerRepository }
- val simBouncerRepository by lazy { kosmos.fakeSimBouncerRepository }
- val clock by lazy { kosmos.systemClock }
- val mobileConnectionsRepository by lazy { kosmos.fakeMobileConnectionsRepository }
- val simBouncerInteractor by lazy { kosmos.simBouncerInteractor }
- val statusBarStateController by lazy { kosmos.statusBarStateController }
- val interactionJankMonitor by lazy { kosmos.interactionJankMonitor }
- val screenOffAnimationController by lazy { kosmos.screenOffAnimationController }
-
- fun fakeSceneContainerRepository(): SceneContainerRepository {
- return kosmos.sceneContainerRepository
- }
-
- fun fakeSceneKeys(): List<SceneKey> {
- return kosmos.sceneKeys
- }
-
- fun fakeSceneContainerConfig(): SceneContainerConfig {
- return kosmos.sceneContainerConfig
- }
-
- fun sceneInteractor(): SceneInteractor {
- return kosmos.sceneInteractor
- }
-
- fun deviceEntryInteractor(): DeviceEntryInteractor {
- return kosmos.deviceEntryInteractor
- }
-
- fun authenticationInteractor(): AuthenticationInteractor {
- return kosmos.authenticationInteractor
- }
-
- fun keyguardInteractor(): KeyguardInteractor {
- return kosmos.keyguardInteractor
- }
-
- fun communalInteractor(): CommunalInteractor {
- return kosmos.communalInteractor
- }
-
- fun bouncerInteractor(): BouncerInteractor {
- return kosmos.bouncerInteractor
- }
-
- fun notificationsPlaceholderViewModel(): NotificationsPlaceholderViewModel {
- return kosmos.notificationsPlaceholderViewModel
- }
-
- fun bouncerViewModel(): BouncerViewModel {
- return kosmos.bouncerViewModel
- }
-
- fun telephonyInteractor(): TelephonyInteractor {
- return kosmos.telephonyInteractor
- }
-
- fun falsingInteractor(): FalsingInteractor {
- return kosmos.falsingInteractor
- }
-
- fun falsingCollector(): FalsingCollector {
- return kosmos.falsingCollector
- }
-
- fun powerInteractor(): PowerInteractor {
- return kosmos.powerInteractor
- }
-
- fun selectedUserInteractor(): SelectedUserInteractor {
- return kosmos.selectedUserInteractor
- }
-
- fun bouncerActionButtonInteractor(): BouncerActionButtonInteractor {
- return kosmos.bouncerActionButtonInteractor
- }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index 0dbade76979c..d7e948eefc95 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -20,11 +20,13 @@ import com.android.systemui.flags.featureFlagsClassic
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.scene.shared.flag.sceneContainerFlags
+import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
val Kosmos.notificationsPlaceholderViewModel by Fixture {
NotificationsPlaceholderViewModel(
interactor = notificationStackAppearanceInteractor,
+ shadeInteractor = shadeInteractor,
flags = sceneContainerFlags,
featureFlags = featureFlagsClassic,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
index d9beabb11ad9..d80ee758269f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/WindowRootViewVisibilityInteractorKosmos.kt
@@ -23,6 +23,7 @@ import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.data.repository.windowRootViewVisibilityRepository
import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.policy.headsUpManager
val Kosmos.windowRootViewVisibilityInteractor by Fixture {
@@ -32,5 +33,6 @@ val Kosmos.windowRootViewVisibilityInteractor by Fixture {
keyguardRepository = keyguardRepository,
headsUpManager = headsUpManager,
powerInteractor = powerInteractor,
+ activeNotificationsInteractor = activeNotificationsInteractor,
)
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
index 2bca2722be6c..24b22f870776 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
@@ -17,6 +17,7 @@ package com.android.systemui.unfold.util
import android.os.Handler
import android.os.Looper
+import androidx.annotation.GuardedBy
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import java.util.concurrent.CopyOnWriteArrayList
@@ -41,8 +42,12 @@ constructor(source: UnfoldTransitionProgressProvider? = null) :
private val listeners = CopyOnWriteArrayList<TransitionProgressListener>()
- @Volatile private var isReadyToHandleTransition = false
- @Volatile private var isTransitionRunning = false
+ private val lock = Object()
+
+ @GuardedBy("lock") private var isReadyToHandleTransition = false
+ // Accessed only from progress thread
+ private var isTransitionRunning = false
+ // Accessed only from progress thread
private var lastTransitionProgress = PROGRESS_UNSET
init {
@@ -72,23 +77,44 @@ constructor(source: UnfoldTransitionProgressProvider? = null) :
* the transition progress events.
*
* Call it with readyToHandleTransition = false when listeners can't process the events.
+ *
+ * Note that this could be called by any thread.
*/
fun setReadyToHandleTransition(isReadyToHandleTransition: Boolean) {
- val progressHandler = this.progressHandler
- if (isTransitionRunning && progressHandler != null) {
- progressHandler.post {
- if (isReadyToHandleTransition) {
- listeners.forEach { it.onTransitionStarted() }
- if (lastTransitionProgress != PROGRESS_UNSET) {
- listeners.forEach { it.onTransitionProgress(lastTransitionProgress) }
- }
- } else {
- isTransitionRunning = false
- listeners.forEach { it.onTransitionFinished() }
+ synchronized(lock) {
+ this.isReadyToHandleTransition = isReadyToHandleTransition
+ val progressHandlerLocal = this.progressHandler
+ if (progressHandlerLocal != null) {
+ ensureInHandler(progressHandlerLocal) { reportLastProgressIfNeeded() }
+ }
+ }
+ }
+
+ /** Runs directly if called from the handler thread. Posts otherwise. */
+ private fun ensureInHandler(handler: Handler, f: () -> Unit) {
+ if (handler.looper.isCurrentThread) {
+ f()
+ } else {
+ handler.post(f)
+ }
+ }
+
+ private fun reportLastProgressIfNeeded() {
+ assertInProgressThread()
+ synchronized(lock) {
+ if (!isTransitionRunning) {
+ return
+ }
+ if (isReadyToHandleTransition) {
+ listeners.forEach { it.onTransitionStarted() }
+ if (lastTransitionProgress != PROGRESS_UNSET) {
+ listeners.forEach { it.onTransitionProgress(lastTransitionProgress) }
}
+ } else {
+ isTransitionRunning = false
+ listeners.forEach { it.onTransitionFinished() }
}
}
- this.isReadyToHandleTransition = isReadyToHandleTransition
}
override fun addCallback(listener: TransitionProgressListener) {
@@ -106,34 +132,42 @@ constructor(source: UnfoldTransitionProgressProvider? = null) :
override fun onTransitionStarted() {
assertInProgressThread()
- isTransitionRunning = true
- if (isReadyToHandleTransition) {
- listeners.forEach { it.onTransitionStarted() }
+ synchronized(lock) {
+ isTransitionRunning = true
+ if (isReadyToHandleTransition) {
+ listeners.forEach { it.onTransitionStarted() }
+ }
}
}
override fun onTransitionProgress(progress: Float) {
assertInProgressThread()
- if (isReadyToHandleTransition) {
- listeners.forEach { it.onTransitionProgress(progress) }
+ synchronized(lock) {
+ if (isReadyToHandleTransition) {
+ listeners.forEach { it.onTransitionProgress(progress) }
+ }
+ lastTransitionProgress = progress
}
- lastTransitionProgress = progress
}
override fun onTransitionFinishing() {
assertInProgressThread()
- if (isReadyToHandleTransition) {
- listeners.forEach { it.onTransitionFinishing() }
+ synchronized(lock) {
+ if (isReadyToHandleTransition) {
+ listeners.forEach { it.onTransitionFinishing() }
+ }
}
}
override fun onTransitionFinished() {
assertInProgressThread()
- if (isReadyToHandleTransition) {
- listeners.forEach { it.onTransitionFinished() }
+ synchronized(lock) {
+ if (isReadyToHandleTransition) {
+ listeners.forEach { it.onTransitionFinished() }
+ }
+ isTransitionRunning = false
+ lastTransitionProgress = PROGRESS_UNSET
}
- isTransitionRunning = false
- lastTransitionProgress = PROGRESS_UNSET
}
private fun assertInProgressThread() {
@@ -151,7 +185,7 @@ constructor(source: UnfoldTransitionProgressProvider? = null) :
}
}
- companion object {
- private const val PROGRESS_UNSET = -1f
+ private companion object {
+ const val PROGRESS_UNSET = -1f
}
}
diff --git a/ravenwood/junit-src/android/platform/test/annotations/ExcludeUnderRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/ExcludeUnderRavenwood.java
new file mode 100644
index 000000000000..2ef381e8682c
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/annotations/ExcludeUnderRavenwood.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Tests marked with this annotation are excluded when running under a Ravenwood test environment.
+ *
+ * A more specific method-level annotation always takes precedence over any class-level
+ * annotation, and an {@link IncludeUnderRavenwood} annotation always takes precedence over
+ * an {@link ExcludeUnderRavenwood} annotation.
+ *
+ * This annotation only takes effect when the containing class has a {@code
+ * RavenwoodRule} configured. Ignoring is accomplished by throwing an {@code org.junit
+ * .AssumptionViolatedException} which test infrastructure treats as being ignored.
+ *
+ * This annotation has no effect on any other non-Ravenwood test environments.
+ *
+ * @hide
+ */
+@Inherited
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface ExcludeUnderRavenwood {
+}
diff --git a/ravenwood/junit-src/android/platform/test/annotations/IncludeUnderRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/IncludeUnderRavenwood.java
new file mode 100644
index 000000000000..0b2e32f67960
--- /dev/null
+++ b/ravenwood/junit-src/android/platform/test/annotations/IncludeUnderRavenwood.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.platform.test.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Tests marked with this annotation are included when running under a Ravenwood test environment.
+ *
+ * A more specific method-level annotation always takes precedence over any class-level
+ * annotation, and an {@link IncludeUnderRavenwood} annotation always takes precedence over
+ * an {@link ExcludeUnderRavenwood} annotation.
+ *
+ * This annotation only takes effect when the containing class has a {@code
+ * RavenwoodRule} configured. Ignoring is accomplished by throwing an {@code org.junit
+ * .AssumptionViolatedException} which test infrastructure treats as being ignored.
+ *
+ * This annotation has no effect on any other non-Ravenwood test environments.
+ *
+ * @hide
+ */
+@Inherited
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface IncludeUnderRavenwood {
+}
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index d96787480cfa..53da8ba14a2c 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -18,7 +18,9 @@ package android.platform.test.ravenwood;
import static org.junit.Assert.fail;
+import android.platform.test.annotations.ExcludeUnderRavenwood;
import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.annotations.IncludeUnderRavenwood;
import org.junit.Assume;
import org.junit.rules.TestRule;
@@ -109,21 +111,52 @@ public class RavenwoodRule implements TestRule {
}
/**
- * Test if the given {@link Description} has been marked with an {@link IgnoreUnderRavenwood}
- * annotation, either at the method or class level.
+ * Determine if the given {@link Description} should be included when running under the
+ * Ravenwood test environment.
+ *
+ * A more specific method-level annotation always takes precedence over any class-level
+ * annotation, and an {@link IncludeUnderRavenwood} annotation always takes precedence over
+ * an {@link ExcludeUnderRavenwood} annotation.
*/
- private static boolean hasIgnoreUnderRavenwoodAnnotation(Description description) {
- if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
+ private boolean shouldIncludeUnderRavenwood(Description description) {
+ // Stopgap for http://g/ravenwood/EPAD-N5ntxM
+ if (description.getMethodName().endsWith("$noRavenwood")) {
+ return false;
+ }
+
+ // First, consult any method-level annotations
+ if (description.getAnnotation(IncludeUnderRavenwood.class) != null) {
return true;
- } else if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
+ }
+ if (description.getAnnotation(ExcludeUnderRavenwood.class) != null) {
+ return false;
+ }
+ if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
+ return false;
+ }
+
+ // Otherwise, consult any class-level annotations
+ if (description.getTestClass().getAnnotation(IncludeUnderRavenwood.class) != null) {
return true;
- } else {
+ }
+ if (description.getTestClass().getAnnotation(ExcludeUnderRavenwood.class) != null) {
+ return false;
+ }
+ if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
return false;
}
+
+ // When no annotations have been requested, assume test should be included
+ return true;
}
@Override
public Statement apply(Statement base, Description description) {
+ // No special treatment when running outside Ravenwood; run tests as-is
+ if (!IS_UNDER_RAVENWOOD) {
+ return base;
+ }
+
if (ENABLE_PROBE_IGNORED) {
return applyProbeIgnored(base, description);
} else {
@@ -138,14 +171,7 @@ public class RavenwoodRule implements TestRule {
return new Statement() {
@Override
public void evaluate() throws Throwable {
- if (hasIgnoreUnderRavenwoodAnnotation(description)) {
- Assume.assumeFalse(IS_UNDER_RAVENWOOD);
- }
-
- // Stopgap for http://g/ravenwood/EPAD-N5ntxM
- if (description.getMethodName().endsWith("$noRavenwood")) {
- Assume.assumeFalse(IS_UNDER_RAVENWOOD);
- }
+ Assume.assumeTrue(shouldIncludeUnderRavenwood(description));
RavenwoodRuleImpl.init(RavenwoodRule.this);
try {
@@ -170,19 +196,17 @@ public class RavenwoodRule implements TestRule {
try {
base.evaluate();
} catch (Throwable t) {
- if (hasIgnoreUnderRavenwoodAnnotation(description)) {
- // This failure is expected, so eat the exception and report the
- // assumption failure that test authors expect
- Assume.assumeFalse(IS_UNDER_RAVENWOOD);
- }
+ // If the test isn't included, eat the exception and report the
+ // assumption failure that test authors expect; otherwise throw
+ Assume.assumeTrue(shouldIncludeUnderRavenwood(description));
throw t;
} finally {
RavenwoodRuleImpl.reset(RavenwoodRule.this);
}
- if (hasIgnoreUnderRavenwoodAnnotation(description) && IS_UNDER_RAVENWOOD) {
- fail("Test was annotated with IgnoreUnderRavenwood, but it actually "
- + "passed under Ravenwood; consider removing the annotation");
+ if (!shouldIncludeUnderRavenwood(description)) {
+ fail("Test wasn't included under Ravenwood, but it actually "
+ + "passed under Ravenwood; consider updating annotations");
}
}
};
diff --git a/services/autofill/features.aconfig b/services/autofill/features.aconfig
index 92e00ee0a477..727721d5319f 100644
--- a/services/autofill/features.aconfig
+++ b/services/autofill/features.aconfig
@@ -5,4 +5,11 @@ flag {
namespace: "autofill"
description: "Guards Autofill Framework against Autofill-Credman integration"
bug: "296907283"
+}
+
+flag {
+ name: "autofill_credman_integration_phase2"
+ namespace: "autofill"
+ description: "Guards against Autofill-Credman integration phase 2"
+ bug: "320730001"
} \ No newline at end of file
diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
index 7fc1738f3172..123470304783 100644
--- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
+++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
@@ -108,9 +108,9 @@ final class SecondaryProviderHandler implements RemoteFillService.FillServiceCal
mLastFlag = flag;
if (mRemoteFillService != null && mRemoteFillService.isCredentialAutofillService()) {
Slog.v(TAG, "About to call CredAutofill service as secondary provider");
- addSessionIdAndRequestIdToClientState(pendingFillRequest,
+ FillRequest request = addSessionIdAndRequestIdToClientState(pendingFillRequest,
pendingInlineSuggestionsRequest, id);
- mRemoteFillService.onFillCredentialRequest(pendingFillRequest, client);
+ mRemoteFillService.onFillCredentialRequest(request, client);
} else {
mRemoteFillService.onFillRequest(pendingFillRequest);
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index a856f424be5c..f3b74ea00a58 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -1735,6 +1735,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
processResponseLockedForPcc(response, response.getClientState(), requestFlags);
mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis();
+ mFillResponseEventLogger.logAndEndEvent();
}
@@ -1847,6 +1848,33 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return;
}
synchronized (mLock) {
+ // TODO(b/319913595): refactor logging for fill response for primary and secondary
+ // providers
+ // Start a new FillResponse logger for the success case.
+ mFillResponseEventLogger.startLogForNewResponse();
+ mFillResponseEventLogger.maybeSetRequestId(fillResponse.getRequestId());
+ mFillResponseEventLogger.maybeSetAppPackageUid(uid);
+ mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SUCCESS);
+ mFillResponseEventLogger.startResponseProcessingTime();
+ // Time passed since session was created
+ final long fillRequestReceivedRelativeTimestamp =
+ SystemClock.elapsedRealtime() - mLatencyBaseTime;
+ mPresentationStatsEventLogger.maybeSetFillResponseReceivedTimestampMs(
+ (int) (fillRequestReceivedRelativeTimestamp));
+ mFillResponseEventLogger.maybeSetLatencyFillResponseReceivedMillis(
+ (int) (fillRequestReceivedRelativeTimestamp));
+ if (mDestroyed) {
+ Slog.w(TAG, "Call to Session#onSecondaryFillResponse() rejected - session: "
+ + id + " destroyed");
+ mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED);
+ mFillResponseEventLogger.logAndEndEvent();
+ return;
+ }
+
+ List<Dataset> datasetList = fillResponse.getDatasets();
+ int datasetCount = (datasetList == null) ? 0 : datasetList.size();
+ mFillResponseEventLogger.maybeSetTotalDatasetsProvided(datasetCount);
+ mFillResponseEventLogger.maybeSetAvailableCount(datasetCount);
if (mSecondaryResponses == null) {
mSecondaryResponses = new SparseArray<>(2);
}
@@ -1859,6 +1887,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
if (currentView != null) {
currentView.maybeCallOnFillReady(flags);
}
+ mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis();
+ mFillResponseEventLogger.logAndEndEvent();
}
}
diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 2fbc3cd24d65..055970819e28 100644
--- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -140,8 +140,8 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
// Widget-related data handled as part of this restore operation
private byte[] mWidgetData;
- // Number of apps restored in this pass
- private int mCount;
+ // Number of apps attempted to restore in this pass
+ private int mRestoreAttemptedAppsCount;
// When did we start?
private long mStartRealtime;
@@ -574,7 +574,8 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
Slog.v(TAG, "No more packages; finishing restore");
}
int millis = (int) (SystemClock.elapsedRealtime() - mStartRealtime);
- EventLog.writeEvent(EventLogTags.RESTORE_SUCCESS, mCount, millis);
+ EventLog.writeEvent(
+ EventLogTags.RESTORE_SUCCESS, mRestoreAttemptedAppsCount, millis);
nextState = UnifiedRestoreState.FINAL;
return;
}
@@ -582,7 +583,8 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
if (DEBUG) {
Slog.i(TAG, "Next restore package: " + mRestoreDescription);
}
- sendOnRestorePackage(pkgName);
+ mRestoreAttemptedAppsCount++;
+ sendOnRestorePackage(mRestoreAttemptedAppsCount, pkgName);
Metadata metaInfo = mPmAgent.getRestoredMetadata(pkgName);
if (metaInfo == null) {
@@ -810,7 +812,6 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
// And then finally start the restore on this agent
try {
initiateOneRestore(mCurrentPackage, metaInfo.versionCode);
- ++mCount;
} catch (Exception e) {
Slog.e(TAG, "Error when attempting restore: " + e.toString());
Bundle monitoringExtras = addRestoreOperationTypeToEvent(/* extras= */ null);
@@ -1331,13 +1332,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
}
// Tell the observer we're done
- if (mObserver != null) {
- try {
- mObserver.restoreFinished(mStatus);
- } catch (RemoteException e) {
- Slog.d(TAG, "Restore observer died at restoreFinished");
- }
- }
+ sendEndRestore();
// Clear any ongoing session timeout.
backupManagerService.getBackupHandler().removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
@@ -1651,10 +1646,10 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
}
}
- void sendOnRestorePackage(String name) {
+ void sendOnRestorePackage(int index, String name) {
if (mObserver != null) {
try {
- mObserver.onUpdate(mCount, name);
+ mObserver.onUpdate(index, name);
} catch (RemoteException e) {
Slog.d(TAG, "Restore observer died in onUpdate");
mObserver = null;
diff --git a/services/companion/java/com/android/server/companion/transport/SecureTransport.java b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
index a0301a920d96..6e906ebe887a 100644
--- a/services/companion/java/com/android/server/companion/transport/SecureTransport.java
+++ b/services/companion/java/com/android/server/companion/transport/SecureTransport.java
@@ -34,7 +34,7 @@ class SecureTransport extends Transport implements SecureChannel.Callback {
private volatile boolean mShouldProcessRequests = false;
- private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(100);
+ private final BlockingQueue<byte[]> mRequestQueue = new ArrayBlockingQueue<>(500);
SecureTransport(int associationId, ParcelFileDescriptor fd, Context context) {
super(associationId, fd, context);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index a6ed498e93db..fdcd27da5bdc 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -30,6 +30,14 @@ filegroup {
],
}
+java_library_static {
+ name: "services-config-update",
+ srcs: [
+ "java/**/ConfigUpdateInstallReceiver.java",
+ "java/**/*.logtags",
+ ],
+}
+
genrule {
name: "services.core.protologsrc",
srcs: [
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 5a44ac803cb4..9d9e7c9345be 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -1387,6 +1387,8 @@ public final class BatteryService extends SystemService {
case BatteryManager.BATTERY_PROPERTY_MANUFACTURING_DATE:
case BatteryManager.BATTERY_PROPERTY_FIRST_USAGE_DATE:
case BatteryManager.BATTERY_PROPERTY_CHARGING_POLICY:
+ case BatteryManager.BATTERY_PROPERTY_SERIAL_NUMBER:
+ case BatteryManager.BATTERY_PROPERTY_PART_STATUS:
mContext.enforceCallingPermission(
android.Manifest.permission.BATTERY_STATS, null);
break;
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 329aac6f3a6a..9f279b1ba3fe 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -48,8 +48,6 @@ import com.android.internal.util.XmlUtils;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
import com.android.server.am.DropboxRateLimiter;
-import com.android.server.os.TombstoneProtos;
-import com.android.server.os.TombstoneProtos.Tombstone;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -62,14 +60,11 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.PosixFilePermissions;
-import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Iterator;
-import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import java.util.stream.Collectors;
/**
* Performs a number of miscellaneous, non-system-critical actions
@@ -337,12 +332,12 @@ public class BootReceiver extends BroadcastReceiver {
*
* @param ctx Context
* @param tombstone path to the tombstone
- * @param tombstoneProto the parsed proto tombstone
+ * @param proto whether the tombstone is stored as proto
* @param processName the name of the process corresponding to the tombstone
* @param tmpFileLock the lock for reading/writing tmp files
*/
public static void addTombstoneToDropBox(
- Context ctx, File tombstone, Tombstone tombstoneProto, String processName,
+ Context ctx, File tombstone, boolean proto, String processName,
ReentrantLock tmpFileLock) {
final DropBoxManager db = ctx.getSystemService(DropBoxManager.class);
if (db == null) {
@@ -352,33 +347,31 @@ public class BootReceiver extends BroadcastReceiver {
// Check if we should rate limit and abort early if needed.
DropboxRateLimiter.RateLimitResult rateLimitResult =
- sDropboxRateLimiter.shouldRateLimit(TAG_TOMBSTONE_PROTO_WITH_HEADERS, processName);
+ sDropboxRateLimiter.shouldRateLimit(
+ proto ? TAG_TOMBSTONE_PROTO_WITH_HEADERS : TAG_TOMBSTONE, processName);
if (rateLimitResult.shouldRateLimit()) return;
HashMap<String, Long> timestamps = readTimestamps();
try {
- // Remove the memory data from the proto.
- Tombstone tombstoneProtoWithoutMemory = removeMemoryFromTombstone(tombstoneProto);
-
- final byte[] tombstoneBytes = tombstoneProtoWithoutMemory.toByteArray();
-
- // Use JNI to call the c++ proto to text converter and add the headers to the tombstone.
- String tombstoneWithoutMemory = new StringBuilder(getBootHeadersToLogAndUpdate())
- .append(rateLimitResult.createHeader())
- .append(getTombstoneText(tombstoneBytes))
- .toString();
-
- // Add the tombstone without memory data to dropbox.
- db.addText(TAG_TOMBSTONE, tombstoneWithoutMemory);
-
- // Add the tombstone proto to dropbox.
- if (recordFileTimestamp(tombstone, timestamps)) {
- tmpFileLock.lock();
- try {
- addAugmentedProtoToDropbox(tombstone, tombstoneBytes, db, rateLimitResult);
- } finally {
- tmpFileLock.unlock();
+ if (proto) {
+ if (recordFileTimestamp(tombstone, timestamps)) {
+ // We need to attach the count indicating the number of dropped dropbox entries
+ // due to rate limiting. Do this by enclosing the proto tombsstone in a
+ // container proto that has the dropped entry count and the proto tombstone as
+ // bytes (to avoid the complexity of reading and writing nested protos).
+ tmpFileLock.lock();
+ try {
+ addAugmentedProtoToDropbox(tombstone, db, rateLimitResult);
+ } finally {
+ tmpFileLock.unlock();
+ }
}
+ } else {
+ // Add the header indicating how many events have been dropped due to rate limiting.
+ final String headers = getBootHeadersToLogAndUpdate()
+ + rateLimitResult.createHeader();
+ addFileToDropBox(db, timestamps, headers, tombstone.getPath(), LOG_SIZE,
+ TAG_TOMBSTONE);
}
} catch (IOException e) {
Slog.e(TAG, "Can't log tombstone", e);
@@ -387,8 +380,11 @@ public class BootReceiver extends BroadcastReceiver {
}
private static void addAugmentedProtoToDropbox(
- File tombstone, byte[] tombstoneBytes, DropBoxManager db,
+ File tombstone, DropBoxManager db,
DropboxRateLimiter.RateLimitResult rateLimitResult) throws IOException {
+ // Read the proto tombstone file as bytes.
+ final byte[] tombstoneBytes = Files.readAllBytes(tombstone.toPath());
+
final File tombstoneProtoWithHeaders = File.createTempFile(
tombstone.getName(), ".tmp", TOMBSTONE_TMP_DIR);
Files.setPosixFilePermissions(
@@ -421,8 +417,6 @@ public class BootReceiver extends BroadcastReceiver {
}
}
- private static native String getTombstoneText(byte[] tombstoneBytes);
-
private static void addLastkToDropBox(
DropBoxManager db, HashMap<String, Long> timestamps,
String headers, String footers, String filename, int maxSize,
@@ -440,31 +434,6 @@ public class BootReceiver extends BroadcastReceiver {
addFileWithFootersToDropBox(db, timestamps, headers, footers, filename, maxSize, tag);
}
- /** Removes memory information from the Tombstone proto. */
- @VisibleForTesting
- public static Tombstone removeMemoryFromTombstone(Tombstone tombstoneProto) {
- Tombstone.Builder tombstoneBuilder = tombstoneProto.toBuilder()
- .clearMemoryMappings()
- .clearThreads()
- .putAllThreads(tombstoneProto.getThreadsMap().entrySet()
- .stream()
- .map(BootReceiver::clearMemoryDump)
- .collect(Collectors.toMap(e->e.getKey(), e->e.getValue())));
-
- if (tombstoneProto.hasSignalInfo()) {
- tombstoneBuilder.setSignalInfo(
- tombstoneProto.getSignalInfo().toBuilder().clearFaultAdjacentMetadata());
- }
-
- return tombstoneBuilder.build();
- }
-
- private static AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread> clearMemoryDump(
- Map.Entry<Integer, TombstoneProtos.Thread> e) {
- return new AbstractMap.SimpleEntry<Integer, TombstoneProtos.Thread>(
- e.getKey(), e.getValue().toBuilder().clearMemoryDump().build());
- }
-
private static void addFileToDropBox(
DropBoxManager db, HashMap<String, Long> timestamps,
String headers, String filename, int maxSize, String tag) throws IOException {
diff --git a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
index 70bd4b328b43..c3916422159e 100644
--- a/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
+++ b/services/core/java/com/android/server/SensitiveContentProtectionManagerService.java
@@ -34,11 +34,11 @@ import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.wm.SensitiveContentPackages.PackageInfo;
import com.android.server.wm.WindowManagerInternal;
-import java.util.Collections;
import java.util.Set;
/**
@@ -54,6 +54,10 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
private @Nullable MediaProjectionManager mProjectionManager;
private @Nullable WindowManagerInternal mWindowManager;
+ final Object mSensitiveContentProtectionLock = new Object();
+ @GuardedBy("mSensitiveContentProtectionLock")
+ private boolean mProjectionActive = false;
+
private final MediaProjectionManager.Callback mProjectionCallback =
new MediaProjectionManager.Callback() {
@Override
@@ -132,14 +136,23 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
}
private void onProjectionStart() {
- StatusBarNotification[] notifications;
- try {
- notifications = mNotificationListener.getActiveNotifications();
- } catch (SecurityException e) {
- Log.e(TAG, "SensitiveContentProtectionManagerService doesn't have access.", e);
- notifications = new StatusBarNotification[0];
+ synchronized (mSensitiveContentProtectionLock) {
+ mProjectionActive = true;
+ updateAppsThatShouldBlockScreenCapture();
}
+ }
+
+ private void onProjectionEnd() {
+ synchronized (mSensitiveContentProtectionLock) {
+ mProjectionActive = false;
+
+ // notify windowmanager to clear any sensitive notifications observed during projection
+ // session
+ mWindowManager.clearBlockedApps();
+ }
+ }
+ private void updateAppsThatShouldBlockScreenCapture() {
RankingMap rankingMap;
try {
rankingMap = mNotificationListener.getCurrentRanking();
@@ -148,41 +161,98 @@ public final class SensitiveContentProtectionManagerService extends SystemServic
rankingMap = null;
}
- // notify windowmanager of any currently posted sensitive content notifications
- Set<PackageInfo> packageInfos = getSensitivePackagesFromNotifications(
- notifications,
- rankingMap);
-
- mWindowManager.setShouldBlockScreenCaptureForApp(packageInfos);
+ updateAppsThatShouldBlockScreenCapture(rankingMap);
}
- private void onProjectionEnd() {
- // notify windowmanager to clear any sensitive notifications observed during projection
- // session
- mWindowManager.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ private void updateAppsThatShouldBlockScreenCapture(RankingMap rankingMap) {
+ StatusBarNotification[] notifications;
+ try {
+ notifications = mNotificationListener.getActiveNotifications();
+ } catch (SecurityException e) {
+ Log.e(TAG, "SensitiveContentProtectionManagerService doesn't have access.", e);
+ notifications = new StatusBarNotification[0];
+ }
+
+ // notify windowmanager of any currently posted sensitive content notifications
+ ArraySet<PackageInfo> packageInfos = getSensitivePackagesFromNotifications(
+ notifications, rankingMap);
+
+ mWindowManager.addBlockScreenCaptureForApps(packageInfos);
}
- private Set<PackageInfo> getSensitivePackagesFromNotifications(
- StatusBarNotification[] notifications, RankingMap rankingMap) {
+ private ArraySet<PackageInfo> getSensitivePackagesFromNotifications(
+ @NonNull StatusBarNotification[] notifications, RankingMap rankingMap) {
+ ArraySet<PackageInfo> sensitivePackages = new ArraySet<>();
if (rankingMap == null) {
Log.w(TAG, "Ranking map not initialized.");
- return Collections.emptySet();
+ return sensitivePackages;
}
- Set<PackageInfo> sensitivePackages = new ArraySet<>();
for (StatusBarNotification sbn : notifications) {
- NotificationListenerService.Ranking ranking =
- rankingMap.getRawRankingObject(sbn.getKey());
- if (ranking != null && ranking.hasSensitiveContent()) {
- PackageInfo info = new PackageInfo(sbn.getPackageName(), sbn.getUid());
+ PackageInfo info = getSensitivePackageFromNotification(sbn, rankingMap);
+ if (info != null) {
sensitivePackages.add(info);
}
}
return sensitivePackages;
}
- // TODO(b/317251408): add trigger that updates on onNotificationPosted,
- // onNotificationRankingUpdate and onListenerConnected
+ private PackageInfo getSensitivePackageFromNotification(StatusBarNotification sbn,
+ RankingMap rankingMap) {
+ if (sbn == null) {
+ Log.w(TAG, "Unable to protect null notification");
+ return null;
+ }
+ if (rankingMap == null) {
+ Log.w(TAG, "Ranking map not initialized.");
+ return null;
+ }
+
+ NotificationListenerService.Ranking ranking = rankingMap.getRawRankingObject(sbn.getKey());
+ if (ranking != null && ranking.hasSensitiveContent()) {
+ return new PackageInfo(sbn.getPackageName(), sbn.getUid());
+ }
+ return null;
+ }
+
@VisibleForTesting
- static class NotificationListener extends NotificationListenerService {}
+ class NotificationListener extends NotificationListenerService {
+ @Override
+ public void onListenerConnected() {
+ super.onListenerConnected();
+ // Projection started before notification listener was connected
+ synchronized (mSensitiveContentProtectionLock) {
+ if (mProjectionActive) {
+ updateAppsThatShouldBlockScreenCapture();
+ }
+ }
+ }
+
+ @Override
+ public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
+ super.onNotificationPosted(sbn, rankingMap);
+ synchronized (mSensitiveContentProtectionLock) {
+ if (!mProjectionActive) {
+ return;
+ }
+
+ // notify windowmanager of any currently posted sensitive content notifications
+ PackageInfo packageInfo = getSensitivePackageFromNotification(sbn, rankingMap);
+
+ if (packageInfo != null) {
+ mWindowManager.addBlockScreenCaptureForApps(new ArraySet(Set.of(packageInfo)));
+ }
+ }
+ }
+
+ @Override
+ public void onNotificationRankingUpdate(RankingMap rankingMap) {
+ super.onNotificationRankingUpdate(rankingMap);
+ synchronized (mSensitiveContentProtectionLock) {
+ if (mProjectionActive) {
+ updateAppsThatShouldBlockScreenCapture(rankingMap);
+ }
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 2b35231a51f8..ea1b0f5f66f7 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -1076,6 +1076,7 @@ class StorageManagerService extends IStorageManager.Stub
final UserManager userManager = mContext.getSystemService(UserManager.class);
final List<UserInfo> users = userManager.getUsers();
+ extendWatchdogTimeout("#onReset might be slow");
mStorageSessionController.onReset(mVold, () -> {
mHandler.removeCallbacksAndMessages(null);
});
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index eb6fdd72f2c3..bd67cf42014a 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -418,6 +418,8 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
LinkCapacityEstimate.INVALID, LinkCapacityEstimate.INVALID)));
private List<List<LinkCapacityEstimate>> mLinkCapacityEstimateLists;
+ private int[] mSimultaneousCellularCallingSubIds = {};
+
private int[] mECBMReason;
private boolean[] mECBMStarted;
private int[] mSCBMReason;
@@ -564,7 +566,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
|| events.contains(TelephonyCallback.EVENT_VOICE_ACTIVATION_STATE_CHANGED)
|| events.contains(TelephonyCallback.EVENT_RADIO_POWER_STATE_CHANGED)
|| events.contains(TelephonyCallback.EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED)
- || events.contains(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED);
+ || events.contains(TelephonyCallback.EVENT_EMERGENCY_CALLBACK_MODE_CHANGED)
+ || events.contains(TelephonyCallback
+ .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED);
}
private static final int MSG_USER_SWITCHED = 1;
@@ -1122,6 +1126,21 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
return;
}
+ int phoneId = -1;
+ int subscriptionId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+ if(Flags.preventSystemServerAndPhoneDeadlock()) {
+ // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
+ // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ if (DBG) {
+ log("invalid subscription id, use default id");
+ }
+ } else { //APP specify subID
+ subscriptionId = subId;
+ }
+ phoneId = getPhoneIdFromSubId(subscriptionId);
+ }
+
synchronized (mRecords) {
// register
IBinder b = callback.asBinder();
@@ -1141,17 +1160,23 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
r.renounceFineLocationAccess = renounceFineLocationAccess;
r.callerUid = Binder.getCallingUid();
r.callerPid = Binder.getCallingPid();
- // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
- // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
- if (!SubscriptionManager.isValidSubscriptionId(subId)) {
- if (DBG) {
- log("invalid subscription id, use default id");
+
+ if(!Flags.preventSystemServerAndPhoneDeadlock()) {
+ // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
+ // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
+ if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+ if (DBG) {
+ log("invalid subscription id, use default id");
+ }
+ r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
+ } else {//APP specify subID
+ r.subId = subId;
}
- r.subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
- } else {//APP specify subID
- r.subId = subId;
+ r.phoneId = getPhoneIdFromSubId(r.subId);
+ } else {
+ r.subId = subscriptionId;
+ r.phoneId = phoneId;
}
- r.phoneId = getPhoneIdFromSubId(r.subId);
r.eventList = events;
if (DBG) {
@@ -1427,6 +1452,15 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
remove(r.binder);
}
}
+ if (events.contains(TelephonyCallback
+ .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED)) {
+ try {
+ r.callback.onSimultaneousCallingStateChanged(
+ mSimultaneousCellularCallingSubIds);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
if (events.contains(
TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED)) {
try {
@@ -1880,8 +1914,14 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
private void notifyCarrierNetworkChangeWithPermission(int subId, boolean active) {
+ int phoneId = -1;
+ if(Flags.preventSystemServerAndPhoneDeadlock()) {
+ phoneId = getPhoneIdFromSubId(subId);
+ }
synchronized (mRecords) {
- int phoneId = getPhoneIdFromSubId(subId);
+ if(!Flags.preventSystemServerAndPhoneDeadlock()) {
+ phoneId = getPhoneIdFromSubId(subId);
+ }
mCarrierNetworkChangeState[phoneId] = active;
if (VDBG) {
@@ -3092,6 +3132,43 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {
}
}
+ /**
+ * Notify the listeners that simultaneous cellular calling subscriptions have changed
+ * @param subIds The set of subIds that support simultaneous cellular calling
+ */
+ public void notifySimultaneousCellularCallingSubscriptionsChanged(int[] subIds) {
+ if (!checkNotifyPermission("notifySimultaneousCellularCallingSubscriptionsChanged()")) {
+ return;
+ }
+
+ if (VDBG) {
+ StringBuilder b = new StringBuilder();
+ b.append("notifySimultaneousCellularCallingSubscriptionsChanged: ");
+ b.append("subIds = {");
+ for (int i : subIds) {
+ b.append(" ");
+ b.append(i);
+ }
+ b.append("}");
+ log(b.toString());
+ }
+
+ synchronized (mRecords) {
+ mSimultaneousCellularCallingSubIds = subIds;
+ for (Record r : mRecords) {
+ if (r.matchTelephonyCallbackEvent(TelephonyCallback
+ .EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED)) {
+ try {
+ r.callback.onSimultaneousCallingStateChanged(subIds);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
@Override
public void addCarrierPrivilegesCallback(
int phoneId,
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 66a10e4a3c11..cd8be338a031 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -16,8 +16,10 @@
package com.android.server;
+import static android.app.Flags.modesApi;
import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
import static android.app.UiModeManager.DEFAULT_PRIORITY;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
import static android.app.UiModeManager.MODE_NIGHT_AUTO;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
@@ -50,6 +52,7 @@ import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.app.UiModeManager;
+import android.app.UiModeManager.AttentionModeThemeOverlayType;
import android.app.UiModeManager.NightModeCustomReturnType;
import android.app.UiModeManager.NightModeCustomType;
import android.content.BroadcastReceiver;
@@ -134,6 +137,7 @@ final class UiModeManagerService extends SystemService {
private int mLastBroadcastState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
private int mNightMode = UiModeManager.MODE_NIGHT_NO;
private int mNightModeCustomType = UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
+ private int mAttentionModeThemeOverlay = UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
private final LocalTime DEFAULT_CUSTOM_NIGHT_START_TIME = LocalTime.of(22, 0);
private final LocalTime DEFAULT_CUSTOM_NIGHT_END_TIME = LocalTime.of(6, 0);
private LocalTime mCustomAutoNightModeStartMilliseconds = DEFAULT_CUSTOM_NIGHT_START_TIME;
@@ -839,6 +843,8 @@ final class UiModeManagerService extends SystemService {
? customModeType
: MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
mNightMode = mode;
+ //deactivates AttentionMode if user toggles DarkTheme
+ mAttentionModeThemeOverlay = MODE_ATTENTION_THEME_OVERLAY_OFF;
resetNightModeOverrideLocked();
persistNightMode(user);
// on screen off will update configuration instead
@@ -879,6 +885,29 @@ final class UiModeManagerService extends SystemService {
}
}
+ @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+ @Override
+ public void setAttentionModeThemeOverlay(
+ @AttentionModeThemeOverlayType int attentionModeThemeOverlayType) {
+ setAttentionModeThemeOverlay_enforcePermission();
+
+ synchronized (mLock) {
+ if (mAttentionModeThemeOverlay != attentionModeThemeOverlayType) {
+ mAttentionModeThemeOverlay = attentionModeThemeOverlayType;
+ Binder.withCleanCallingIdentity(()-> updateLocked(0, 0));
+ }
+ }
+ }
+
+ @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_DAY_NIGHT_MODE)
+ @Override
+ public @AttentionModeThemeOverlayType int getAttentionModeThemeOverlay() {
+ getAttentionModeThemeOverlay_enforcePermission();
+ synchronized (mLock) {
+ return mAttentionModeThemeOverlay;
+ }
+ }
+
@Override
public void setApplicationNightMode(@UiModeManager.NightMode int mode) {
switch (mode) {
@@ -1406,7 +1435,7 @@ final class UiModeManagerService extends SystemService {
pw.print(Shell.nightModeToStr(mNightMode, mNightModeCustomType)); pw.print(") ");
pw.print(" mOverrideOn/Off="); pw.print(mOverrideNightModeOn);
pw.print("/"); pw.print(mOverrideNightModeOff);
-
+ pw.print(" mAttentionModeThemeOverlay="); pw.print(mAttentionModeThemeOverlay);
pw.print(" mNightModeLocked="); pw.println(mNightModeLocked);
pw.print(" mCarModeEnabled="); pw.print(mCarModeEnabled);
@@ -1685,7 +1714,7 @@ final class UiModeManagerService extends SystemService {
}
@UiModeManager.NightMode
- private int getComputedUiModeConfiguration(@UiModeManager.NightMode int uiMode) {
+ private int getComputedUiModeConfiguration(int uiMode) {
uiMode |= mComputedNightMode ? Configuration.UI_MODE_NIGHT_YES
: Configuration.UI_MODE_NIGHT_NO;
uiMode &= mComputedNightMode ? ~Configuration.UI_MODE_NIGHT_NO
@@ -1980,18 +2009,26 @@ final class UiModeManagerService extends SystemService {
}
private void updateComputedNightModeLocked(boolean activate) {
- mComputedNightMode = activate;
- if (mNightMode == MODE_NIGHT_YES || mNightMode == UiModeManager.MODE_NIGHT_NO) {
- return;
- }
- if (mOverrideNightModeOn && !mComputedNightMode) {
- mComputedNightMode = true;
- return;
- }
- if (mOverrideNightModeOff && mComputedNightMode) {
- mComputedNightMode = false;
- return;
+ boolean newComputedValue = activate;
+ if (mNightMode != MODE_NIGHT_YES && mNightMode != UiModeManager.MODE_NIGHT_NO) {
+ if (mOverrideNightModeOn && !newComputedValue) {
+ newComputedValue = true;
+ } else if (mOverrideNightModeOff && newComputedValue) {
+ newComputedValue = false;
+ }
+ }
+
+ if (modesApi()) {
+ // Computes final night mode values based on Attention Mode.
+ mComputedNightMode = switch (mAttentionModeThemeOverlay) {
+ case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT) -> true;
+ case (UiModeManager.MODE_ATTENTION_THEME_OVERLAY_DAY) -> false;
+ default -> newComputedValue; // case OFF
+ };
+ } else {
+ mComputedNightMode = newComputedValue;
}
+
if (mNightMode != MODE_NIGHT_AUTO || (mTwilightManager != null
&& mTwilightManager.getLastTwilightState() != null)) {
resetNightModeOverrideLocked();
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index fd17261bda41..c18bacb51671 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -172,6 +172,7 @@ public class Watchdog implements Dumpable {
public static final String[] AIDL_INTERFACE_PREFIXES_OF_INTEREST = new String[] {
"android.hardware.audio.core.IModule/",
"android.hardware.audio.core.IConfig/",
+ "android.hardware.audio.effect.IFactory/",
"android.hardware.biometrics.face.IFace/",
"android.hardware.biometrics.fingerprint.IFingerprint/",
"android.hardware.bluetooth.IBluetoothHci/",
diff --git a/services/core/java/com/android/server/ZramWriteback.java b/services/core/java/com/android/server/ZramWriteback.java
index 5d97def129a4..39ca29eaad43 100644
--- a/services/core/java/com/android/server/ZramWriteback.java
+++ b/services/core/java/com/android/server/ZramWriteback.java
@@ -177,7 +177,6 @@ public final class ZramWriteback extends JobService {
// back at later point if they remain untouched.
js.schedule(new JobInfo.Builder(MARK_IDLE_JOB_ID, sZramWriteback)
.setMinimumLatency(TimeUnit.MINUTES.toMillis(markIdleDelay))
- .setOverrideDeadline(TimeUnit.MINUTES.toMillis(markIdleDelay))
.build());
// Schedule a one time job to flush idle pages to disk.
diff --git a/services/core/java/com/android/server/adaptiveauth/OWNERS b/services/core/java/com/android/server/adaptiveauth/OWNERS
new file mode 100644
index 000000000000..b18810564d88
--- /dev/null
+++ b/services/core/java/com/android/server/adaptiveauth/OWNERS
@@ -0,0 +1 @@
+hainingc@google.com \ No newline at end of file
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 979449ffc179..9b1fade198fc 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -5400,13 +5400,13 @@ public final class ActiveServices {
return msg;
}
mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
- hostingRecord, true);
+ true);
if (isolated) {
r.isolationHostProc = app;
}
} else {
mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
- hostingRecord, false);
+ false);
}
if (r.fgRequired) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f6d954a69044..c9bd0b4c4648 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -7823,12 +7823,7 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public void registerUidObserver(IUidObserver observer, int which, int cutpoint,
String callingPackage) {
- if (!hasUsageStatsPermission(callingPackage)) {
- enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS,
- "registerUidObserver");
- }
- mUidObserverController.register(observer, which, cutpoint, callingPackage,
- Binder.getCallingUid(), /*uids*/null);
+ registerUidObserverForUids(observer, which, cutpoint, callingPackage, null /* uids */);
}
/**
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index b90e5cd11216..c85723525aa1 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -81,18 +81,18 @@ public final class AppStartInfoTracker {
private static final int FOREACH_ACTION_REMOVE_ITEM = 1;
private static final int FOREACH_ACTION_STOP_ITERATION = 2;
- private static final int APP_START_INFO_HISTORY_LIST_SIZE = 16;
+ @VisibleForTesting static final int APP_START_INFO_HISTORY_LIST_SIZE = 16;
@VisibleForTesting static final String APP_START_STORE_DIR = "procstartstore";
@VisibleForTesting static final String APP_START_INFO_FILE = "procstartinfo";
- private final Object mLock = new Object();
+ @VisibleForTesting final Object mLock = new Object();
- private boolean mEnabled = false;
+ @VisibleForTesting boolean mEnabled = false;
/** Initialized in {@link #init} and read-only after that. */
- private ActivityManagerService mService;
+ @VisibleForTesting ActivityManagerService mService;
/** Initialized in {@link #init} and read-only after that. */
private Handler mHandler;
@@ -112,7 +112,7 @@ public final class AppStartInfoTracker {
*
* <p>Initialized in {@link #init} and read-only after that. No lock is needed.
*/
- private int mAppStartInfoHistoryListSize;
+ @VisibleForTesting int mAppStartInfoHistoryListSize;
@GuardedBy("mLock")
private final ProcessMap<AppStartInfoContainer> mData;
@@ -146,7 +146,8 @@ public final class AppStartInfoTracker {
* Key is timestamp of launch from {@link #ActivityMetricsLaunchObserver}.
*/
@GuardedBy("mLock")
- private ArrayMap<Long, ApplicationStartInfo> mInProgRecords = new ArrayMap<>();
+ @VisibleForTesting
+ final ArrayMap<Long, ApplicationStartInfo> mInProgRecords = new ArrayMap<>();
AppStartInfoTracker() {
mCallbacks = new SparseArray<>();
@@ -229,7 +230,7 @@ public final class AppStartInfoTracker {
ApplicationStartInfo info = mInProgRecords.get(id);
info.setStartType((int) temperature);
addBaseFieldsFromProcessRecord(info, app);
- addStartInfoLocked(info);
+ mInProgRecords.put(id, addStartInfoLocked(info));
} else {
mInProgRecords.remove(id);
}
@@ -262,6 +263,7 @@ public final class AppStartInfoTracker {
ApplicationStartInfo info = mInProgRecords.get(id);
info.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN);
info.setLaunchMode(launchMode);
+ checkCompletenessAndCallback(info);
}
}
@@ -281,7 +283,7 @@ public final class AppStartInfoTracker {
}
public void handleProcessServiceStart(long startTimeNs, ProcessRecord app,
- ServiceRecord serviceRecord, HostingRecord hostingRecord, boolean cold) {
+ ServiceRecord serviceRecord, boolean cold) {
synchronized (mLock) {
if (!mEnabled) {
return;
@@ -297,7 +299,9 @@ public final class AppStartInfoTracker {
&& serviceRecord.permission.contains("android.permission.BIND_JOB_SERVICE")
? ApplicationStartInfo.START_REASON_JOB
: ApplicationStartInfo.START_REASON_SERVICE);
- start.setIntent(serviceRecord.intent.getIntent());
+ if (serviceRecord.intent != null) {
+ start.setIntent(serviceRecord.intent.getIntent());
+ }
addStartInfoLocked(start);
}
}
@@ -378,6 +382,7 @@ public final class AppStartInfoTracker {
start.setPackageUid(app.info.uid);
start.setDefiningUid(definingUid > 0 ? definingUid : app.info.uid);
start.setProcessName(app.processName);
+ start.setPackageName(app.info.packageName);
}
void reportApplicationOnCreateTimeNanos(ProcessRecord app, long timeNs) {
@@ -419,12 +424,12 @@ public final class AppStartInfoTracker {
}
private void addTimestampToStart(ProcessRecord app, long timeNs, int key) {
- addTimestampToStart(app.processName, app.uid, timeNs, key);
+ addTimestampToStart(app.info.packageName, app.uid, timeNs, key);
}
- private void addTimestampToStart(String processName, int uid, long timeNs, int key) {
+ private void addTimestampToStart(String packageName, int uid, long timeNs, int key) {
synchronized (mLock) {
- AppStartInfoContainer container = mData.get(processName, uid);
+ AppStartInfoContainer container = mData.get(packageName, uid);
if (container == null) {
// Record was not created, discard new data.
return;
@@ -443,11 +448,11 @@ public final class AppStartInfoTracker {
final ApplicationStartInfo info = new ApplicationStartInfo(raw);
- AppStartInfoContainer container = mData.get(raw.getProcessName(), raw.getRealUid());
+ AppStartInfoContainer container = mData.get(raw.getPackageName(), raw.getRealUid());
if (container == null) {
container = new AppStartInfoContainer(mAppStartInfoHistoryListSize);
container.mUid = info.getRealUid();
- mData.put(raw.getProcessName(), raw.getRealUid(), container);
+ mData.put(raw.getPackageName(), raw.getRealUid(), container);
}
container.addStartInfoLocked(info);
@@ -486,6 +491,9 @@ public final class AppStartInfoTracker {
if (!mEnabled) {
return;
}
+ if (maxNum == 0) {
+ maxNum = APP_START_INFO_HISTORY_LIST_SIZE;
+ }
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -891,6 +899,7 @@ public final class AppStartInfoTracker {
mProcStartInfoFile.delete();
}
mData.getMap().clear();
+ mInProgRecords.clear();
}
}
@@ -960,6 +969,10 @@ public final class AppStartInfoTracker {
/** Convenience method to obtain timestamp of beginning of start.*/
private static long getStartTimestamp(ApplicationStartInfo startInfo) {
+ if (startInfo.getStartupTimestamps() == null
+ || !startInfo.getStartupTimestamps().containsKey(START_TIMESTAMP_LAUNCH)) {
+ return -1;
+ }
return startInfo.getStartupTimestamps().get(START_TIMESTAMP_LAUNCH);
}
@@ -997,7 +1010,6 @@ public final class AppStartInfoTracker {
if (oldestIndex >= 0) {
mInfos.remove(oldestIndex);
}
- mInfos.remove(0);
}
mInfos.add(info);
Collections.sort(mInfos, (a, b) ->
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index b03183cb37d5..fa5dbd2543d3 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2935,7 +2935,11 @@ public final class ProcessList {
return true;
}
- private static void freezeBinderAndPackageCgroup(ArrayList<Pair<ProcessRecord, Boolean>> procs,
+ private static boolean unfreezePackageCgroup(int packageUID) {
+ return freezePackageCgroup(packageUID, false);
+ }
+
+ private static void freezeBinderAndPackageCgroup(List<Pair<ProcessRecord, Boolean>> procs,
int packageUID) {
// Freeze all binder processes under the target UID (whose cgroup is about to be frozen).
// Since we're going to kill these, we don't need to unfreze them later.
@@ -2943,12 +2947,9 @@ public final class ProcessList {
// processes (forks) should not be Binder users.
int N = procs.size();
for (int i = 0; i < N; i++) {
- final int uid = procs.get(i).first.uid;
final int pid = procs.get(i).first.getPid();
int nRetries = 0;
- // We only freeze the cgroup of the target package, so we do not need to freeze the
- // Binder interfaces of dependant processes in other UIDs.
- if (pid > 0 && uid == packageUID) {
+ if (pid > 0) {
try {
int rc;
do {
@@ -2962,12 +2963,19 @@ public final class ProcessList {
}
// We freeze the entire UID (parent) cgroup so that newly-specialized processes also freeze
- // despite being added to a new child cgroup. The cgroups of package dependant processes are
- // not frozen, since it's possible this would freeze processes with no dependency on the
- // package being killed here.
+ // despite being added to a child cgroup created after this call that would otherwise be
+ // unfrozen.
freezePackageCgroup(packageUID, true);
}
+ private static List<Pair<ProcessRecord, Boolean>> getUIDSublist(
+ List<Pair<ProcessRecord, Boolean>> procs, int startIdx) {
+ final int uid = procs.get(startIdx).first.uid;
+ int endIdx = startIdx + 1;
+ while (endIdx < procs.size() && procs.get(endIdx).first.uid == uid) ++endIdx;
+ return procs.subList(startIdx, endIdx);
+ }
+
@GuardedBy({"mService", "mProcLock"})
boolean killPackageProcessesLSP(String packageName, int appId,
int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
@@ -3063,25 +3071,36 @@ public final class ProcessList {
}
}
- final int packageUID = UserHandle.getUid(userId, appId);
- final boolean doFreeze = appId >= Process.FIRST_APPLICATION_UID
- && appId <= Process.LAST_APPLICATION_UID;
- if (doFreeze) {
- freezeBinderAndPackageCgroup(procs, packageUID);
+ final boolean killingUserApp = appId >= Process.FIRST_APPLICATION_UID
+ && appId <= Process.LAST_APPLICATION_UID;
+
+ if (killingUserApp) {
+ procs.sort((o1, o2) -> Integer.compare(o1.first.uid, o2.first.uid));
}
- int N = procs.size();
- for (int i=0; i<N; i++) {
- final Pair<ProcessRecord, Boolean> proc = procs.get(i);
- removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
- reasonCode, subReason, reason, !doFreeze /* async */);
+ int idx = 0;
+ while (idx < procs.size()) {
+ final List<Pair<ProcessRecord, Boolean>> uidProcs = getUIDSublist(procs, idx);
+ final int packageUID = uidProcs.get(0).first.uid;
+
+ // Do not freeze for system apps or for dependencies of the targeted package, but
+ // make sure to freeze the targeted package for all users if called with USER_ALL.
+ final boolean doFreeze = killingUserApp && UserHandle.getAppId(packageUID) == appId;
+
+ if (doFreeze) freezeBinderAndPackageCgroup(uidProcs, packageUID);
+
+ for (Pair<ProcessRecord, Boolean> proc : uidProcs) {
+ removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
+ reasonCode, subReason, reason, !doFreeze /* async */);
+ }
+ killAppZygotesLocked(packageName, appId, userId, false /* force */);
+
+ if (doFreeze) unfreezePackageCgroup(packageUID);
+
+ idx += uidProcs.size();
}
- killAppZygotesLocked(packageName, appId, userId, false /* force */);
mService.updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
- if (doFreeze) {
- freezePackageCgroup(packageUID, false);
- }
- return N > 0;
+ return procs.size() > 0;
}
@GuardedBy("mService")
diff --git a/services/core/java/com/android/server/app/GameManagerSettings.java b/services/core/java/com/android/server/app/GameManagerSettings.java
index 5189017f5bf0..b084cf3c3b12 100644
--- a/services/core/java/com/android/server/app/GameManagerSettings.java
+++ b/services/core/java/com/android/server/app/GameManagerSettings.java
@@ -251,6 +251,7 @@ public class GameManagerSettings {
+ type);
}
}
+ str.close();
} catch (XmlPullParserException | java.io.IOException e) {
Slog.wtf(TAG, "Error reading game manager settings", e);
return false;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index df8d9e1a406c..2ed217a89397 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -65,6 +65,9 @@ import static android.app.AppOpsManager.opRestrictsRead;
import static android.app.AppOpsManager.opToName;
import static android.app.AppOpsManager.opToPublicName;
import static android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT;
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.EXTRA_REPLACING;
import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
import static android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP;
@@ -973,7 +976,29 @@ public class AppOpsService extends IAppOpsService.Stub {
String pkgName = intent.getData().getEncodedSchemeSpecificPart();
int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
- if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
+ if (action.equals(ACTION_PACKAGE_ADDED)
+ && !intent.getBooleanExtra(EXTRA_REPLACING, false)) {
+ PackageInfo pi = getPackageManagerInternal().getPackageInfo(pkgName,
+ PackageManager.GET_PERMISSIONS, Process.myUid(),
+ UserHandle.getUserId(uid));
+ boolean isSamplingTarget = isSamplingTarget(pi);
+ synchronized (AppOpsService.this) {
+ if (isSamplingTarget) {
+ mRarelyUsedPackages.add(pkgName);
+ }
+ UidState uidState = getUidStateLocked(uid, true);
+ if (!uidState.pkgOps.containsKey(pkgName)) {
+ uidState.pkgOps.put(pkgName,
+ new Ops(pkgName, uidState));
+ }
+
+ createSandboxUidStateIfNotExistsForAppLocked(uid);
+ }
+ } else if (action.equals(ACTION_PACKAGE_REMOVED) && !intent.hasExtra(EXTRA_REPLACING)) {
+ synchronized (AppOpsService.this) {
+ packageRemovedLocked(uid, pkgName);
+ }
+ } else if (action.equals(Intent.ACTION_PACKAGE_REPLACED)) {
AndroidPackage pkg = getPackageManagerInternal().getPackage(pkgName);
if (pkg == null) {
return;
@@ -1052,7 +1077,9 @@ public class AppOpsService extends IAppOpsService.Stub {
mHistoricalRegistry.systemReady(mContext.getContentResolver());
IntentFilter packageUpdateFilter = new IntentFilter();
+ packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED);
packageUpdateFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ packageUpdateFilter.addAction(ACTION_PACKAGE_REMOVED);
packageUpdateFilter.addDataScheme("package");
mContext.registerReceiverAsUser(mOnPackageUpdatedReceiver, UserHandle.ALL,
@@ -1079,7 +1106,7 @@ public class AppOpsService extends IAppOpsService.Stub {
String action;
if (!ArrayUtils.contains(pkgsInUid, pkg)) {
- action = Intent.ACTION_PACKAGE_REMOVED;
+ action = ACTION_PACKAGE_REMOVED;
} else {
action = Intent.ACTION_PACKAGE_REPLACED;
}
@@ -1160,44 +1187,6 @@ public class AppOpsService extends IAppOpsService.Stub {
// onUserRemoved handled by #removeUser
});
-
- getPackageManagerInternal().getPackageList(
- new PackageManagerInternal.PackageListObserver() {
- @Override
- public void onPackageAdded(String packageName, int appId) {
- PackageInfo pi = getPackageManagerInternal().getPackageInfo(packageName,
- PackageManager.GET_PERMISSIONS, Process.myUid(),
- mContext.getUserId());
- boolean isSamplingTarget = isSamplingTarget(pi);
- int[] userIds = getUserManagerInternal().getUserIds();
- synchronized (AppOpsService.this) {
- if (isSamplingTarget) {
- mRarelyUsedPackages.add(packageName);
- }
- for (int i = 0; i < userIds.length; i++) {
- int uid = UserHandle.getUid(userIds[i], appId);
- UidState uidState = getUidStateLocked(uid, true);
- if (!uidState.pkgOps.containsKey(packageName)) {
- uidState.pkgOps.put(packageName,
- new Ops(packageName, uidState));
- }
-
- createSandboxUidStateIfNotExistsForAppLocked(uid);
- }
- }
- }
-
- @Override
- public void onPackageRemoved(String packageName, int appId) {
- int[] userIds = getUserManagerInternal().getUserIds();
- synchronized (AppOpsService.this) {
- for (int i = 0; i < userIds.length; i++) {
- int uid = UserHandle.getUid(userIds[i], appId);
- packageRemovedLocked(uid, packageName);
- }
- }
- }
- });
}
/**
@@ -2893,6 +2882,10 @@ public class AppOpsService extends IAppOpsService.Stub {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
+ if (proxyAttributionTag != null
+ && !isAttributionTagDefined(packageName, proxyPackageName, proxyAttributionTag)) {
+ proxyAttributionTag = null;
+ }
synchronized (this) {
final Ops ops = getOpsLocked(uid, packageName, attributionTag,
@@ -3487,6 +3480,10 @@ public class AppOpsService extends IAppOpsService.Stub {
return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
packageName);
}
+ if (proxyAttributionTag != null
+ && !isAttributionTagDefined(packageName, proxyPackageName, proxyAttributionTag)) {
+ proxyAttributionTag = null;
+ }
boolean isRestricted = false;
int startType = START_TYPE_FAILED;
@@ -4340,6 +4337,36 @@ public class AppOpsService extends IAppOpsService.Stub {
return false;
}
+ /**
+ * Checks to see if the attribution tag is defined in either package or proxyPackage.
+ * This method is intended for ProxyAttributionTag validation and returns false
+ * if it does not exist in either one of them.
+ *
+ * @param packageName Name of the package
+ * @param proxyPackageName Name of the proxy package
+ * @param attributionTag attribution tag to be checked
+ *
+ * @return boolean specifying if attribution tag is valid or not
+ */
+ private boolean isAttributionTagDefined(@Nullable String packageName,
+ @Nullable String proxyPackageName,
+ @Nullable String attributionTag) {
+ if (packageName == null) {
+ return false;
+ } else if (attributionTag == null) {
+ return true;
+ }
+ PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+ if (proxyPackageName != null) {
+ AndroidPackage proxyPkg = pmInt.getPackage(proxyPackageName);
+ if (proxyPkg != null && isAttributionInPackage(proxyPkg, attributionTag)) {
+ return true;
+ }
+ }
+ AndroidPackage pkg = pmInt.getPackage(packageName);
+ return isAttributionInPackage(pkg, attributionTag);
+ }
+
private void logVerifyAndGetBypassFailure(int uid, @NonNull SecurityException e,
@NonNull String methodName) {
if (Process.isIsolated(uid)) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index f80228afa52d..99b45ec79571 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1812,22 +1812,21 @@ public class AudioDeviceBroker {
"msg: MSG_L_SET_BT_ACTIVE_DEVICE "
+ "received with null profile proxy: "
+ btInfo)).printLog(TAG));
- sendMsg(MSG_CHECK_MUTE_MUSIC, SENDMSG_REPLACE, 0 /*delay*/);
- return;
- }
- @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
- mBtHelper.getCodecWithFallback(btInfo.mDevice,
- btInfo.mProfile, btInfo.mIsLeOutput,
- "MSG_L_SET_BT_ACTIVE_DEVICE");
- mDeviceInventory.onSetBtActiveDevice(btInfo, codec,
- (btInfo.mProfile
- != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
- ? mAudioService.getBluetoothContextualVolumeStream()
- : AudioSystem.STREAM_DEFAULT);
- if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
- || btInfo.mProfile == BluetoothProfile.HEARING_AID) {
- onUpdateCommunicationRouteClient(isBluetoothScoRequested(),
- "setBluetoothActiveDevice");
+ } else {
+ @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec =
+ mBtHelper.getCodecWithFallback(btInfo.mDevice,
+ btInfo.mProfile, btInfo.mIsLeOutput,
+ "MSG_L_SET_BT_ACTIVE_DEVICE");
+ mDeviceInventory.onSetBtActiveDevice(btInfo, codec,
+ (btInfo.mProfile
+ != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput)
+ ? mAudioService.getBluetoothContextualVolumeStream()
+ : AudioSystem.STREAM_DEFAULT);
+ if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
+ || btInfo.mProfile == BluetoothProfile.HEARING_AID) {
+ onUpdateCommunicationRouteClient(isBluetoothScoRequested(),
+ "setBluetoothActiveDevice");
+ }
}
}
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index bf20ae3b516d..57b19cda7c12 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -764,7 +764,7 @@ public class AudioDeviceInventory {
/** only public for mocking/spying, do not call outside of AudioService */
// @GuardedBy("mDeviceBroker.mSetModeLock")
@VisibleForTesting
- @GuardedBy("mDeviceBroker.mDeviceStateLock")
+ //@GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
public void onSetBtActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo btInfo,
@AudioSystem.AudioFormatNativeEnumForBtCodec int codec,
int streamType) {
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index d5d8fd22314b..8fd2ee2bdc33 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -20,6 +20,7 @@ package com.android.server.biometrics;
// TODO(b/141025588): Create separate internal and external permissions for AuthService.
// TODO(b/141025588): Get rid of the USE_FINGERPRINT permission.
+import static android.Manifest.permission.MANAGE_BIOMETRIC_DIALOG;
import static android.Manifest.permission.TEST_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
@@ -304,6 +305,9 @@ public class AuthService extends SystemService {
if (promptInfo.containsPrivateApiConfigurations()) {
checkInternalPermission();
}
+ if (promptInfo.containsManageBioApiConfigurations()) {
+ checkManageBiometricPermission();
+ }
final long identity = Binder.clearCallingIdentity();
try {
@@ -984,6 +988,11 @@ public class AuthService extends SystemService {
"Must have USE_BIOMETRIC_INTERNAL permission");
}
+ private void checkManageBiometricPermission() {
+ getContext().enforceCallingOrSelfPermission(MANAGE_BIOMETRIC_DIALOG,
+ "Must have MANAGE_BIOMETRIC_DIALOG permission");
+ }
+
private void checkPermission() {
if (getContext().checkCallingOrSelfPermission(USE_FINGERPRINT)
!= PackageManager.PERMISSION_GRANTED) {
diff --git a/services/core/java/com/android/server/broadcastradio/TEST_MAPPING b/services/core/java/com/android/server/broadcastradio/TEST_MAPPING
new file mode 100644
index 000000000000..ee4eeb634c84
--- /dev/null
+++ b/services/core/java/com/android/server/broadcastradio/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "imports": [
+ {
+ "path": "frameworks/base/core/tests/BroadcastRadioTests"
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
index 1f59b57d2da9..c260f10b61a6 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionManagerInternal.java
@@ -17,6 +17,7 @@
package com.android.server.grammaticalinflection;
import android.annotation.Nullable;
+import android.content.res.Configuration;
/**
* System-server internal interface to the {@link android.app.GrammaticalInflectionManager}.
@@ -37,5 +38,14 @@ public abstract class GrammaticalInflectionManagerInternal {
* at the time this is called, to be referenced later when the app is installed.
*/
public abstract void stageAndApplyRestoredPayload(byte[] payload, int userId);
+
+ /**
+ * Get the current system grammatical gender of privileged application.
+ *
+ * @return the value of grammatical gender
+ *
+ * @see Configuration#getGrammaticalGender
+ */
+ public abstract @Configuration.GrammaticalGender int getSystemGrammaticalGender(int userId);
}
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
index 68848a2ad426..6eb7e9559b8d 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -19,9 +19,12 @@ package com.android.server.grammaticalinflection;
import static android.app.Flags.systemTermsOfAddressEnabled;
import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+import static com.android.server.grammaticalinflection.GrammaticalInflectionUtils.checkSystemGrammaticalGenderPermission;
+
import android.annotation.Nullable;
import android.app.GrammaticalInflectionManager;
import android.app.IGrammaticalInflectionManager;
+import android.content.AttributionSource;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
@@ -30,6 +33,7 @@ import android.os.Process;
import android.os.ResultReceiver;
import android.os.ShellCallback;
import android.os.SystemProperties;
+import android.permission.PermissionManager;
import android.util.AtomicFile;
import android.util.Log;
import android.util.SparseIntArray;
@@ -75,6 +79,8 @@ public class GrammaticalInflectionService extends SystemService {
private PackageManagerInternal mPackageManagerInternal;
private GrammaticalInflectionService.GrammaticalInflectionBinderService mBinderService;
+ private PermissionManager mPermissionManager;
+ private Context mContext;
/**
* Initializes the system service.
@@ -88,11 +94,12 @@ public class GrammaticalInflectionService extends SystemService {
*/
public GrammaticalInflectionService(Context context) {
super(context);
+ mContext = context;
mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
- mBackupHelper = new GrammaticalInflectionBackupHelper(
- this, context.getPackageManager());
+ mBackupHelper = new GrammaticalInflectionBackupHelper(this, context.getPackageManager());
mBinderService = new GrammaticalInflectionBinderService();
+ mPermissionManager = context.getSystemService(PermissionManager.class);
}
@Override
@@ -112,7 +119,7 @@ public class GrammaticalInflectionService extends SystemService {
}
@Override
- public void setSystemWideGrammaticalGender(int userId, int grammaticalGender) {
+ public void setSystemWideGrammaticalGender(int grammaticalGender, int userId) {
checkCallerIsSystem();
checkSystemTermsOfAddressIsEnabled();
GrammaticalInflectionService.this.setSystemWideGrammaticalGender(grammaticalGender,
@@ -120,16 +127,17 @@ public class GrammaticalInflectionService extends SystemService {
}
@Override
- public int getSystemGrammaticalGender(int userId) {
+ public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
checkSystemTermsOfAddressIsEnabled();
- return GrammaticalInflectionService.this.getSystemGrammaticalGender(userId);
+ return GrammaticalInflectionService.this.getSystemGrammaticalGender(attributionSource,
+ userId);
}
@Override
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
- (new GrammaticalInflectionShellCommand(mBinderService))
+ (new GrammaticalInflectionShellCommand(mBinderService, mContext.getAttributionSource()))
.exec(this, in, out, err, args, callback, resultReceiver);
}
};
@@ -148,6 +156,13 @@ public class GrammaticalInflectionService extends SystemService {
public void stageAndApplyRestoredPayload(byte[] payload, int userId) {
mBackupHelper.stageAndApplyRestoredPayload(payload, userId);
}
+
+ @Override
+ public int getSystemGrammaticalGender(int userId) {
+ checkCallerIsSystem();
+ return GrammaticalInflectionService.this.getSystemGrammaticalGender(
+ mContext.getAttributionSource(), userId);
+ }
}
protected int getApplicationGrammaticalGender(String appPackageName, int userId) {
@@ -211,9 +226,24 @@ public class GrammaticalInflectionService extends SystemService {
}
}
- // TODO(b/298591009): Add a new AppOp value for the apps that want to access the grammatical
- // gender.
- public int getSystemGrammaticalGender(int userId) {
+ public int getSystemGrammaticalGender(AttributionSource attributionSource, int userId) {
+ String packageName = attributionSource.getPackageName();
+ if (packageName == null) {
+ Log.d(TAG, "Package name is null.");
+ return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ }
+
+ int callingUid = Binder.getCallingUid();
+ if (mPackageManagerInternal.getPackageUid(packageName, 0, userId) != callingUid) {
+ Log.d(TAG,
+ "Package " + packageName + " does not belong to the calling uid " + callingUid);
+ return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ }
+
+ if (!checkSystemGrammaticalGenderPermission(mPermissionManager, attributionSource)) {
+ return GRAMMATICAL_GENDER_NOT_SPECIFIED;
+ }
+
synchronized (mLock) {
final File file = getGrammaticalGenderFile(userId);
if (!file.exists()) {
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
index d22372860ead..cdda69278b2c 100644
--- a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionShellCommand.java
@@ -21,6 +21,7 @@ import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED
import android.app.ActivityManager;
import android.app.GrammaticalInflectionManager;
import android.app.IGrammaticalInflectionManager;
+import android.content.AttributionSource;
import android.content.res.Configuration;
import android.os.RemoteException;
import android.os.ShellCommand;
@@ -44,9 +45,12 @@ class GrammaticalInflectionShellCommand extends ShellCommand {
}
private final IGrammaticalInflectionManager mBinderService;
+ private AttributionSource mAttributionSource;
- GrammaticalInflectionShellCommand(IGrammaticalInflectionManager grammaticalInflectionManager) {
+ GrammaticalInflectionShellCommand(IGrammaticalInflectionManager grammaticalInflectionManager,
+ AttributionSource attributionSource) {
mBinderService = grammaticalInflectionManager;
+ mAttributionSource = attributionSource;
}
@Override
@@ -115,7 +119,7 @@ class GrammaticalInflectionShellCommand extends ShellCommand {
} while (true);
try {
- mBinderService.setSystemWideGrammaticalGender(userId, grammaticalGender);
+ mBinderService.setSystemWideGrammaticalGender(grammaticalGender, userId);
} catch (RemoteException e) {
getOutPrintWriter().println("Remote Exception: " + e);
}
@@ -141,7 +145,8 @@ class GrammaticalInflectionShellCommand extends ShellCommand {
} while (true);
try {
- int grammaticalGender = mBinderService.getSystemGrammaticalGender(userId);
+ int grammaticalGender = mBinderService.getSystemGrammaticalGender(mAttributionSource,
+ userId);
getOutPrintWriter().println(GRAMMATICAL_GENDER_MAP.get(grammaticalGender));
} catch (RemoteException e) {
getOutPrintWriter().println("Remote Exception: " + e);
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionUtils.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionUtils.java
new file mode 100644
index 000000000000..f056561f20e0
--- /dev/null
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionUtils.java
@@ -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.server.grammaticalinflection;
+
+import static android.Manifest.permission.READ_SYSTEM_GRAMMATICAL_GENDER;
+
+import android.annotation.NonNull;
+import android.content.AttributionSource;
+import android.permission.PermissionManager;
+import android.util.Log;
+
+/**
+ * Utility methods for system grammatical gender.
+ */
+public class GrammaticalInflectionUtils {
+
+ private static final String TAG = "GrammaticalInflectionUtils";
+
+ public static boolean checkSystemGrammaticalGenderPermission(
+ @NonNull PermissionManager permissionManager,
+ @NonNull AttributionSource attributionSource) {
+ int permissionCheckResult = permissionManager.checkPermissionForDataDelivery(
+ READ_SYSTEM_GRAMMATICAL_GENDER,
+ attributionSource, /* message= */ null);
+ if (permissionCheckResult != PermissionManager.PERMISSION_GRANTED) {
+ Log.v(TAG, "AttributionSource: " + attributionSource
+ + " does not have READ_SYSTEM_GRAMMATICAL_GENDER permission.");
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index e827866368fe..10c01864457e 100755
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -140,7 +140,13 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction {
wrapUpAndFinish();
return;
}
-
+ // Check if the action was finished before the callback was called.
+ // See {@link HdmiCecFeatureAction#finish}.
+ if (mState == STATE_NONE) {
+ Slog.v(TAG, "Action was already finished before the callback was called.");
+ wrapUpAndFinish();
+ return;
+ }
Slog.v(TAG, "Device detected: " + ackedAddress);
allocateDevices(ackedAddress);
if (mDelayPeriod > 0) {
@@ -453,7 +459,6 @@ final class DeviceDiscoveryAction extends HdmiCecFeatureAction {
wrapUpAndFinish();
return;
}
-
// If finished current stage, move on to next stage.
if (mProcessedDeviceCount == mDevices.size()) {
mProcessedDeviceCount = 0;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index 9087354dee40..a79fb88ea8b6 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -160,6 +160,9 @@ final class HdmiCecController {
// This variable is used for testing, in order to delay the logical address allocation.
private long mLogicalAddressAllocationDelay = 0;
+ // This variable is used for testing, in order to delay polling devices.
+ private long mPollDevicesDelay = 0;
+
// Private constructor. Use HdmiCecController.create().
private HdmiCecController(
HdmiControlService service, NativeWrapper nativeWrapper, HdmiCecAtomWriter atomWriter) {
@@ -463,6 +466,14 @@ final class HdmiCecController {
}
/**
+ * This method is used for testing, in order to delay polling devices.
+ */
+ @VisibleForTesting
+ void setPollDevicesDelay(long delay) {
+ mPollDevicesDelay = delay;
+ }
+
+ /**
* Returns true if the language code is well-formed.
*/
@VisibleForTesting static boolean isLanguage(String language) {
@@ -523,7 +534,10 @@ final class HdmiCecController {
// Extract polling candidates. No need to poll against local devices.
List<Integer> pollingCandidates = pickPollCandidates(pickStrategy);
ArrayList<Integer> allocated = new ArrayList<>();
- runDevicePolling(sourceAddress, pollingCandidates, retryCount, callback, allocated);
+ mControlHandler.postDelayed(
+ () -> runDevicePolling(
+ sourceAddress, pollingCandidates, retryCount, callback, allocated),
+ mPollDevicesDelay);
}
private List<Integer> pickPollCandidates(int pickStrategy) {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 1cd267dee2fe..d34661d4d6ac 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1290,15 +1290,19 @@ public final class HdmiCecLocalDeviceTv extends HdmiCecLocalDevice {
mService.getHdmiCecNetwork().removeCecSwitches(portId);
}
- // Turning System Audio Mode off when the AVR is unlugged or standby.
- // When the device is not unplugged but reawaken from standby, we check if the System
- // Audio Control Feature is enabled or not then decide if turning SAM on/off accordingly.
- if (getAvrDeviceInfo() != null && portId == getAvrDeviceInfo().getPortId()) {
- HdmiLogger.debug("Port ID:%d, 5v=%b", portId, connected);
- if (!connected) {
- setSystemAudioMode(false);
- } else {
- onNewAvrAdded(getAvrDeviceInfo());
+ if (!mService.isEarcEnabled() || !mService.isEarcSupported()) {
+ HdmiDeviceInfo avr = getAvrDeviceInfo();
+ if (avr != null
+ && portId == avr.getPortId()
+ && isConnectedToArcPort(avr.getPhysicalAddress())) {
+ HdmiLogger.debug("Port ID:%d, 5v=%b", portId, connected);
+ if (connected) {
+ if (mArcEstablished) {
+ enableAudioReturnChannel(true);
+ }
+ } else {
+ enableAudioReturnChannel(false);
+ }
}
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index eaf754dc7520..e0e825d9147a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -3617,7 +3617,7 @@ public class HdmiControlService extends SystemService {
}
}
- @VisibleForTesting
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
protected boolean isEarcSupported() {
synchronized (mLock) {
return mEarcSupported;
diff --git a/services/core/java/com/android/server/hdmi/NewDeviceAction.java b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
index f3532e5ce7e9..b6c0e5d970a1 100644
--- a/services/core/java/com/android/server/hdmi/NewDeviceAction.java
+++ b/services/core/java/com/android/server/hdmi/NewDeviceAction.java
@@ -53,6 +53,7 @@ final class NewDeviceAction extends HdmiCecFeatureAction {
private int mVendorId;
private String mDisplayName;
private int mTimeoutRetry;
+ private HdmiDeviceInfo mOldDeviceInfo;
/**
* Constructor.
@@ -73,6 +74,38 @@ final class NewDeviceAction extends HdmiCecFeatureAction {
@Override
public boolean start() {
+ mOldDeviceInfo =
+ localDevice().mService.getHdmiCecNetwork().getCecDeviceInfo(mDeviceLogicalAddress);
+ // If there's deviceInfo with same (logical address, physical address) set
+ // Then addCecDevice should be delayed until system information process is finished
+ if (mOldDeviceInfo != null
+ && mOldDeviceInfo.getPhysicalAddress() == mDevicePhysicalAddress) {
+ Slog.d(TAG, "Start NewDeviceAction with old deviceInfo:["
+ + mOldDeviceInfo.toString() + "]");
+ } else {
+ // Add the device ahead with default information to handle <Active Source>
+ // promptly, rather than waiting till the new device action is finished.
+ Slog.d(TAG, "Start NewDeviceAction with default deviceInfo");
+ HdmiDeviceInfo deviceInfo = HdmiDeviceInfo.cecDeviceBuilder()
+ .setLogicalAddress(mDeviceLogicalAddress)
+ .setPhysicalAddress(mDevicePhysicalAddress)
+ .setPortId(tv().getPortId(mDevicePhysicalAddress))
+ .setDeviceType(mDeviceType)
+ .setVendorId(Constants.VENDOR_ID_UNKNOWN)
+ .build();
+ // If a deviceInfo with same logical address but different physical address exists
+ // We should remove the old deviceInfo first
+ // This will happen if the interval between unplugging and plugging device is too short
+ // and HotplugDetection Action fails to remove the old deviceInfo, or when the newly
+ // plugged device violates HDMI Spec and uses an occupied logical address
+ if (mOldDeviceInfo != null) {
+ Slog.d(TAG, "Remove device by NewDeviceAction, logical address conflicts: "
+ + mDevicePhysicalAddress);
+ localDevice().mService.getHdmiCecNetwork().removeCecDevice(
+ localDevice(), mDeviceLogicalAddress);
+ }
+ localDevice().mService.getHdmiCecNetwork().addCecDevice(deviceInfo);
+ }
requestOsdName(true);
return true;
}
@@ -182,14 +215,30 @@ final class NewDeviceAction extends HdmiCecFeatureAction {
.setVendorId(mVendorId)
.setDisplayName(mDisplayName)
.build();
- localDevice().mService.getHdmiCecNetwork().updateCecDevice(deviceInfo);
- // Consume CEC messages we already got for this newly found device.
- tv().processDelayedMessages(mDeviceLogicalAddress);
+ // Check if oldDevice is same as newDevice
+ // If so, don't add newDevice info, preventing ARC or HDMI source re-connection
+ if (mOldDeviceInfo != null
+ && mOldDeviceInfo.getLogicalAddress() == mDeviceLogicalAddress
+ && mOldDeviceInfo.getPhysicalAddress() == mDevicePhysicalAddress
+ && mOldDeviceInfo.getDeviceType() == mDeviceType
+ && mOldDeviceInfo.getVendorId() == mVendorId
+ && mOldDeviceInfo.getDisplayName().equals(mDisplayName)) {
+ // Consume CEC messages we already got for this newly found device.
+ tv().processDelayedMessages(mDeviceLogicalAddress);
+ Slog.d(TAG, "Ignore NewDevice, deviceInfo is same as current device");
+ Slog.d(TAG, "Old:[" + mOldDeviceInfo.toString()
+ + "]; New:[" + deviceInfo.toString() + "]");
+ } else {
+ Slog.d(TAG, "Add NewDevice:[" + deviceInfo.toString() + "]");
+ localDevice().mService.getHdmiCecNetwork().addCecDevice(deviceInfo);
- if (HdmiUtils.isEligibleAddressForDevice(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM,
- mDeviceLogicalAddress)) {
- tv().onNewAvrAdded(deviceInfo);
+ // Consume CEC messages we already got for this newly found device.
+ tv().processDelayedMessages(mDeviceLogicalAddress);
+ if (HdmiUtils.isEligibleAddressForDevice(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM,
+ mDeviceLogicalAddress)) {
+ tv().onNewAvrAdded(deviceInfo);
+ }
}
}
diff --git a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
index 1153cc37e3da..8a3a56cdc9ca 100644
--- a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
+++ b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
@@ -16,6 +16,8 @@
package com.android.server.health;
+import static android.os.Flags.batteryPartStatusApi;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.health.BatteryHealthData;
@@ -150,6 +152,18 @@ class HealthServiceWrapperAidl extends HealthServiceWrapper {
healthData = service.getBatteryHealthData();
prop.setLong(healthData.batteryStateOfHealth);
break;
+ case BatteryManager.BATTERY_PROPERTY_SERIAL_NUMBER:
+ if (batteryPartStatusApi()) {
+ healthData = service.getBatteryHealthData();
+ prop.setString(healthData.batterySerialNumber);
+ }
+ break;
+ case BatteryManager.BATTERY_PROPERTY_PART_STATUS:
+ if (batteryPartStatusApi()) {
+ healthData = service.getBatteryHealthData();
+ prop.setLong(healthData.batteryPartStatus);
+ }
+ break;
}
} catch (UnsupportedOperationException e) {
// Leave prop untouched.
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 6236e2b933bc..46668de042d4 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -1370,7 +1370,7 @@ class KeyboardLayoutManager implements InputManager.InputDeviceListener {
public boolean isVirtualDevice(int deviceId) {
VirtualDeviceManagerInternal vdm = LocalServices.getService(
VirtualDeviceManagerInternal.class);
- return vdm == null || vdm.isInputDeviceOwnedByVirtualDevice(deviceId);
+ return vdm != null && vdm.isInputDeviceOwnedByVirtualDevice(deviceId);
}
private static int[] getScriptCodes(@Nullable Locale locale) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
index 295793569a25..a7632519b7bc 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodInfoUtils.java
@@ -77,8 +77,6 @@ final class InputMethodInfoUtils {
return this;
}
- // TODO: The behavior of InputMethodSubtype#overridesImplicitlyEnabledSubtype() should be
- // documented more clearly.
InputMethodListBuilder fillAuxiliaryImes(ArrayList<InputMethodInfo> imis, Context context) {
// If one or more auxiliary input methods are available, OK to stop populating the list.
for (final InputMethodInfo imi : mInputMethodSet) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 43058080d84d..834ba20b84fd 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -168,7 +168,7 @@ final class InputMethodSubtypeSwitchingController {
final ArrayList<InputMethodInfo> imis = settings.getEnabledInputMethodListLocked();
if (imis.isEmpty()) {
- return Collections.emptyList();
+ return new ArrayList<>();
}
if (isScreenLocked && includeAuxiliarySubtypes) {
if (DEBUG) {
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 21e7befc1b89..0ae60360883d 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -44,6 +44,7 @@ import android.util.Log;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import com.android.media.flags.Flags;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -318,11 +319,19 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider
@Override
public void onBindingDied(ComponentName name) {
- if (DEBUG) {
- Slog.d(TAG, this + ": Service binding died");
- }
unbind();
- if (shouldBind()) {
+ if (Flags.enablePreventionOfKeepAliveRouteProviders()) {
+ Slog.w(
+ TAG,
+ TextUtils.formatSimple(
+ "Route provider service (%s) binding died, but we did not rebind.",
+ name.toString()));
+ } else if (shouldBind()) {
+ Slog.w(
+ TAG,
+ TextUtils.formatSimple(
+ "Rebound to provider service (%s) after binding died.",
+ name.toString()));
bind();
}
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
index bd252e7fdfd2..3d717a871cf1 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderWatcher.java
@@ -33,6 +33,8 @@ import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
+import com.android.media.flags.Flags;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -86,7 +88,9 @@ final class MediaRoute2ProviderWatcher {
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
- filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ if (!Flags.enablePreventionOfKeepAliveRouteProviders()) {
+ filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
+ }
filter.addDataScheme("package");
mContext.registerReceiverAsUser(mScanPackagesReceiver,
new UserHandle(mUserId), filter, null, mHandler);
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 f6571d94d554..550aed51c8e2 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -304,7 +304,7 @@ public final class MediaProjectionManagerService extends SystemService
}
@VisibleForTesting
- void addCallback(final IMediaProjectionWatcherCallback callback) {
+ MediaProjectionInfo addCallback(final IMediaProjectionWatcherCallback callback) {
IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
@@ -314,6 +314,7 @@ public final class MediaProjectionManagerService extends SystemService
synchronized (mLock) {
mCallbackDelegate.add(callback);
linkDeathRecipientLocked(callback, deathRecipient);
+ return mProjectionGrant != null ? mProjectionGrant.getProjectionInfo() : null;
}
}
@@ -786,11 +787,11 @@ public final class MediaProjectionManagerService extends SystemService
@Override //Binder call
@EnforcePermission(MANAGE_MEDIA_PROJECTION)
- public void addCallback(final IMediaProjectionWatcherCallback callback) {
+ public MediaProjectionInfo addCallback(final IMediaProjectionWatcherCallback callback) {
addCallback_enforcePermission();
final long token = Binder.clearCallingIdentity();
try {
- MediaProjectionManagerService.this.addCallback(callback);
+ return MediaProjectionManagerService.this.addCallback(callback);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1244,7 +1245,7 @@ public final class MediaProjectionManagerService extends SystemService
}
public MediaProjectionInfo getProjectionInfo() {
- return new MediaProjectionInfo(packageName, userHandle);
+ return new MediaProjectionInfo(packageName, userHandle, mLaunchCookie);
}
boolean requiresForegroundService() {
diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
index 4d19eade5a05..d7188c7f10c6 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java
@@ -42,7 +42,6 @@ import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
-import com.android.internal.annotations.Keep;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.RingBuffer;
import com.android.server.am.ProcessList;
@@ -414,7 +413,7 @@ public class NetworkPolicyLogger {
private static final Date sDate = new Date();
public LogBuffer(int capacity) {
- super(Data.class, capacity);
+ super(Data::new, Data[]::new, capacity);
}
public void uidStateChanged(int uid, int procState, long procStateSeq,
@@ -690,12 +689,8 @@ public class NetworkPolicyLogger {
/**
* Container class for all networkpolicy events data.
- *
- * Note: This class needs to be public for RingBuffer class to be able to create
- * new instances of this.
*/
- @Keep
- public static final class Data {
+ private static final class Data {
public int type;
public long timeStamp;
diff --git a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
index 71a6b5ed0581..ab650afe68a7 100644
--- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
+++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java
@@ -16,7 +16,8 @@
package com.android.server.notification;
-import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
import android.app.UiModeManager;
import android.app.WallpaperManager;
@@ -128,10 +129,9 @@ class DefaultDeviceEffectsApplier implements DeviceEffectsApplier {
private void updateNightModeImmediately(boolean useNightMode) {
Binder.withCleanCallingIdentity(() -> {
- // TODO: b/314285749 - Placeholder; use real APIs when available.
- mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
- mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME,
- useNightMode);
+ mUiModeManager.setAttentionModeThemeOverlay(
+ useNightMode ? MODE_ATTENTION_THEME_OVERLAY_NIGHT
+ : MODE_ATTENTION_THEME_OVERLAY_OFF);
});
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index c9f45e523e3b..2ae040a69583 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5931,8 +5931,7 @@ public class NotificationManagerService extends SystemService {
newVisualEffects, policy.priorityConversationSenders);
if (shouldApplyAsImplicitRule) {
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy,
- origin);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, callingUid, policy);
} else {
ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion,
policy);
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index c7f570353a5b..93ffd974bb80 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -32,6 +32,7 @@ import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR
import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
+import static com.android.internal.util.Preconditions.checkArgument;
import android.annotation.DrawableRes;
import android.annotation.NonNull;
@@ -427,6 +428,7 @@ public class ZenModeHelper {
public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule,
@ConfigChangeOrigin int origin, String reason, int callingUid) {
+ requirePublicOrigin("addAutomaticZenRule", origin);
if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) {
PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner());
if (component == null) {
@@ -525,6 +527,7 @@ public class ZenModeHelper {
public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule,
@ConfigChangeOrigin int origin, String reason, int callingUid) {
+ requirePublicOrigin("updateAutomaticZenRule", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return false;
@@ -602,7 +605,11 @@ public class ZenModeHelper {
rule = newImplicitZenRule(callingPkg);
newConfig.automaticRules.put(rule.id, rule);
}
- rule.zenMode = zenMode;
+ // If the user has changed the rule's *zenMode*, then don't let app overwrite it.
+ // We allow the update if the user has only changed other aspects of the rule.
+ if ((rule.userModifiedFields & AutomaticZenRule.FIELD_INTERRUPTION_FILTER) == 0) {
+ rule.zenMode = zenMode;
+ }
rule.snoozing = false;
rule.condition = new Condition(rule.conditionId,
mContext.getString(R.string.zen_mode_implicit_activated),
@@ -625,7 +632,7 @@ public class ZenModeHelper {
* {@link Global#ZEN_MODE_IMPORTANT_INTERRUPTIONS}.
*/
void applyGlobalPolicyAsImplicitZenRule(String callingPkg, int callingUid,
- NotificationManager.Policy policy, @ConfigChangeOrigin int origin) {
+ NotificationManager.Policy policy) {
if (!android.app.Flags.modesApi()) {
Log.wtf(TAG, "applyGlobalPolicyAsImplicitZenRule called with flag off!");
return;
@@ -641,10 +648,17 @@ public class ZenModeHelper {
rule.zenMode = Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
newConfig.automaticRules.put(rule.id, rule);
}
- // TODO: b/308673679 - Keep user customization of this rule!
- rule.zenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy);
- setConfigLocked(newConfig, /* triggeringComponent= */ null, origin,
- "applyGlobalPolicyAsImplicitZenRule", callingUid);
+ // If the user has changed the rule's *ZenPolicy*, then don't let app overwrite it.
+ // We allow the update if the user has only changed other aspects of the rule.
+ if (rule.zenPolicyUserModifiedFields == 0) {
+ updatePolicy(
+ rule,
+ ZenAdapters.notificationPolicyToZenPolicy(policy),
+ /* updateBitmask= */ false);
+
+ setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP,
+ "applyGlobalPolicyAsImplicitZenRule", callingUid);
+ }
}
}
@@ -726,6 +740,7 @@ public class ZenModeHelper {
boolean removeAutomaticZenRule(String id, @ConfigChangeOrigin int origin, String reason,
int callingUid) {
+ requirePublicOrigin("removeAutomaticZenRule", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return false;
@@ -758,6 +773,7 @@ public class ZenModeHelper {
boolean removeAutomaticZenRules(String packageName, @ConfigChangeOrigin int origin,
String reason, int callingUid) {
+ requirePublicOrigin("removeAutomaticZenRules", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return false;
@@ -806,6 +822,7 @@ public class ZenModeHelper {
void setAutomaticZenRuleState(String id, Condition condition, @ConfigChangeOrigin int origin,
int callingUid) {
+ requirePublicOrigin("setAutomaticZenRuleState", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return;
@@ -819,6 +836,7 @@ public class ZenModeHelper {
void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition,
@ConfigChangeOrigin int origin, int callingUid) {
+ requirePublicOrigin("setAutomaticZenRuleState", origin);
ZenModeConfig newConfig;
synchronized (mConfigLock) {
if (mConfig == null) return;
@@ -988,7 +1006,7 @@ public class ZenModeHelper {
return null;
}
- void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
+ private void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule,
@ConfigChangeOrigin int origin, boolean isNew) {
if (Flags.modesApi()) {
// These values can always be edited by the app, so we apply changes immediately.
@@ -1053,11 +1071,9 @@ public class ZenModeHelper {
rule.zenMode = newZenMode;
// Updates the bitmask and values for all policy fields, based on the origin.
- rule.zenPolicy = updatePolicy(rule.zenPolicy, automaticZenRule.getZenPolicy(),
- updateBitmask);
+ updatePolicy(rule, automaticZenRule.getZenPolicy(), updateBitmask);
// Updates the bitmask and values for all device effect fields, based on the origin.
- rule.zenDeviceEffects = updateZenDeviceEffects(
- rule.zenDeviceEffects, automaticZenRule.getDeviceEffects(),
+ updateZenDeviceEffects(rule, automaticZenRule.getDeviceEffects(),
origin == UPDATE_ORIGIN_APP, updateBitmask);
} else {
if (rule.enabled != automaticZenRule.isEnabled()) {
@@ -1069,13 +1085,6 @@ public class ZenModeHelper {
rule.enabled = automaticZenRule.isEnabled();
rule.modified = automaticZenRule.isModified();
rule.zenPolicy = automaticZenRule.getZenPolicy();
- if (Flags.modesApi()) {
- rule.zenDeviceEffects = updateZenDeviceEffects(
- rule.zenDeviceEffects,
- automaticZenRule.getDeviceEffects(),
- origin == UPDATE_ORIGIN_APP,
- origin == UPDATE_ORIGIN_USER);
- }
rule.zenMode = NotificationManager.zenModeFromInterruptionFilter(
automaticZenRule.getInterruptionFilter(), Global.ZEN_MODE_OFF);
rule.configurationActivity = automaticZenRule.getConfigurationActivity();
@@ -1099,28 +1108,28 @@ public class ZenModeHelper {
}
/**
- * Modifies {@link ZenPolicy} that is being stored as part of a new or updated ZenRule.
- * Returns a policy based on {@code oldPolicy}, but with fields updated to match
- * {@code newPolicy} where they differ, and updating the internal user-modified bitmask to
- * track these changes, if applicable based on {@code origin}.
+ * Modifies the {@link ZenPolicy} associated to a new or updated ZenRule.
+ *
+ * <p>The new policy is {@code newPolicy}, while the user-modified bitmask is updated to reflect
+ * the changes being applied (if applicable, i.e. if the update is from the user).
*/
- @Nullable
- private ZenPolicy updatePolicy(@Nullable ZenPolicy oldPolicy, @Nullable ZenPolicy newPolicy,
- boolean updateBitmask) {
- // If the update is to make the policy null, we don't need to update the bitmask,
- // because it won't be stored anywhere anyway.
+ private void updatePolicy(ZenRule zenRule, @Nullable ZenPolicy newPolicy,
+ boolean updateBitmask) {
if (newPolicy == null) {
- return null;
+ // TODO: b/319242206 - Treat as newPolicy == default policy and continue below.
+ zenRule.zenPolicy = null;
+ return;
}
// If oldPolicy is null, we compare against the default policy when determining which
// fields in the bitmask should be marked as updated.
- if (oldPolicy == null) {
- oldPolicy = mDefaultConfig.toZenPolicy();
- }
+ ZenPolicy oldPolicy =
+ zenRule.zenPolicy != null ? zenRule.zenPolicy : mDefaultConfig.toZenPolicy();
+
+ zenRule.zenPolicy = newPolicy;
- int userModifiedFields = oldPolicy.getUserModifiedFields();
if (updateBitmask) {
+ int userModifiedFields = zenRule.zenPolicyUserModifiedFields;
if (oldPolicy.getPriorityMessageSenders() != newPolicy.getPriorityMessageSenders()) {
userModifiedFields |= ZenPolicy.FIELD_MESSAGES;
}
@@ -1178,66 +1187,47 @@ public class ZenModeHelper {
!= newPolicy.getVisualEffectNotificationList()) {
userModifiedFields |= ZenPolicy.FIELD_VISUAL_EFFECT_NOTIFICATION_LIST;
}
+ zenRule.zenPolicyUserModifiedFields = userModifiedFields;
}
-
- // After all bitmask changes have been made, sets the bitmask.
- return new ZenPolicy.Builder(newPolicy).setUserModifiedFields(userModifiedFields).build();
}
/**
- * Modifies {@link ZenDeviceEffects} that are being stored as part of a new or updated ZenRule.
- * Returns a {@link ZenDeviceEffects} based on {@code oldEffects}, but with fields updated to
- * match {@code newEffects} where they differ, and updating the internal user-modified bitmask
- * to track these changes, if applicable based on {@code origin}.
- * <ul>
- * <li> Apps cannot turn on hidden effects (those tagged as {@code @hide}) since they are
- * intended for platform-specific rules (e.g. wearables). If it's a new rule, we blank them
- * out; if it's an update, we preserve the previous values.
- * </ul>
+ * Modifies the {@link ZenDeviceEffects} associated to a new or updated ZenRule.
+ *
+ * <p>The new value is {@code newEffects}, while the user-modified bitmask is updated to reflect
+ * the changes being applied (if applicable, i.e. if the update is from the user).
+ *
+ * <p>Apps cannot turn on hidden effects (those tagged as {@code @hide}), so those fields are
+ * treated especially: for a new rule, they are blanked out; for an updated rule, previous
+ * values are preserved.
*/
- @Nullable
- private static ZenDeviceEffects updateZenDeviceEffects(@Nullable ZenDeviceEffects oldEffects,
- @Nullable ZenDeviceEffects newEffects,
- boolean isFromApp,
- boolean updateBitmask) {
+ private static void updateZenDeviceEffects(ZenRule zenRule,
+ @Nullable ZenDeviceEffects newEffects, boolean isFromApp, boolean updateBitmask) {
if (newEffects == null) {
- return null;
+ zenRule.zenDeviceEffects = null;
+ return;
}
- // Since newEffects is not null, we want to adopt all the new provided device effects.
- ZenDeviceEffects.Builder builder = new ZenDeviceEffects.Builder(newEffects);
+ ZenDeviceEffects oldEffects = zenRule.zenDeviceEffects != null
+ ? zenRule.zenDeviceEffects
+ : new ZenDeviceEffects.Builder().build();
if (isFromApp) {
- if (oldEffects != null) {
- // We can do this because we know we don't need to update the bitmask FROM_APP.
- return builder
- .setShouldDisableAutoBrightness(oldEffects.shouldDisableAutoBrightness())
- .setShouldDisableTapToWake(oldEffects.shouldDisableTapToWake())
- .setShouldDisableTiltToWake(oldEffects.shouldDisableTiltToWake())
- .setShouldDisableTouch(oldEffects.shouldDisableTouch())
- .setShouldMinimizeRadioUsage(oldEffects.shouldMinimizeRadioUsage())
- .setShouldMaximizeDoze(oldEffects.shouldMaximizeDoze())
- .build();
- } else {
- return builder
- .setShouldDisableAutoBrightness(false)
- .setShouldDisableTapToWake(false)
- .setShouldDisableTiltToWake(false)
- .setShouldDisableTouch(false)
- .setShouldMinimizeRadioUsage(false)
- .setShouldMaximizeDoze(false)
- .build();
- }
+ // Don't allow apps to toggle hidden effects.
+ newEffects = new ZenDeviceEffects.Builder(newEffects)
+ .setShouldDisableAutoBrightness(oldEffects.shouldDisableAutoBrightness())
+ .setShouldDisableTapToWake(oldEffects.shouldDisableTapToWake())
+ .setShouldDisableTiltToWake(oldEffects.shouldDisableTiltToWake())
+ .setShouldDisableTouch(oldEffects.shouldDisableTouch())
+ .setShouldMinimizeRadioUsage(oldEffects.shouldMinimizeRadioUsage())
+ .setShouldMaximizeDoze(oldEffects.shouldMaximizeDoze())
+ .build();
}
- // If oldEffects is null, we compare against the default device effects object when
- // determining which fields in the bitmask should be marked as updated.
- if (oldEffects == null) {
- oldEffects = new ZenDeviceEffects.Builder().build();
- }
+ zenRule.zenDeviceEffects = newEffects;
- int userModifiedFields = oldEffects.getUserModifiedFields();
if (updateBitmask) {
+ int userModifiedFields = zenRule.zenDeviceEffectsUserModifiedFields;
if (oldEffects.shouldDisplayGrayscale() != newEffects.shouldDisplayGrayscale()) {
userModifiedFields |= ZenDeviceEffects.FIELD_GRAYSCALE;
}
@@ -1270,11 +1260,8 @@ public class ZenModeHelper {
if (oldEffects.shouldMaximizeDoze() != newEffects.shouldMaximizeDoze()) {
userModifiedFields |= ZenDeviceEffects.FIELD_MAXIMIZE_DOZE;
}
+ zenRule.zenDeviceEffectsUserModifiedFields = userModifiedFields;
}
-
- // Since newEffects is not null, we want to adopt all the new provided device effects.
- // Set the usermodifiedFields value separately, to reflect the updated bitmask.
- return builder.setUserModifiedFields(userModifiedFields).build();
}
private AutomaticZenRule zenRuleToAutomaticZenRule(ZenRule rule) {
@@ -1293,7 +1280,6 @@ public class ZenModeHelper {
.setOwner(rule.component)
.setConfigurationActivity(rule.configurationActivity)
.setTriggerDescription(rule.triggerDescription)
- .setUserModifiedFields(rule.userModifiedFields)
.build();
} else {
azr = new AutomaticZenRule(rule.name, rule.component,
@@ -2369,6 +2355,19 @@ public class ZenModeHelper {
return null;
}
}
+
+ /** Checks that the {@code origin} supplied to a ZenModeHelper "API" method makes sense. */
+ private static void requirePublicOrigin(String method, @ConfigChangeOrigin int origin) {
+ if (!Flags.modesApi()) {
+ return;
+ }
+ checkArgument(origin == UPDATE_ORIGIN_APP || origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI
+ || origin == UPDATE_ORIGIN_USER,
+ "Expected one of UPDATE_ORIGIN_APP, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, or "
+ + "UPDATE_ORIGIN_USER for %s, but received '%s'.",
+ method, origin);
+ }
+
private final class Metrics extends Callback {
private static final String COUNTER_MODE_PREFIX = "dnd_mode_";
private static final String COUNTER_TYPE_PREFIX = "dnd_type_";
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index 9ce3cb3abe4a..f6e7ef3d50e9 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -41,13 +41,14 @@ import android.system.Os;
import android.system.StructStat;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoParseException;
import com.android.internal.annotations.GuardedBy;
import com.android.server.BootReceiver;
import com.android.server.ServiceThread;
import com.android.server.os.TombstoneProtos.Cause;
import com.android.server.os.TombstoneProtos.Tombstone;
-import com.android.server.os.protobuf.CodedInputStream;
import libcore.io.IoUtils;
@@ -129,21 +130,18 @@ public final class NativeTombstoneManager {
return;
}
+ String processName = "UNKNOWN";
final boolean isProtoFile = filename.endsWith(".pb");
- if (!isProtoFile) {
- return;
- }
+ File protoPath = isProtoFile ? path : new File(path.getAbsolutePath() + ".pb");
- Optional<ParsedTombstone> parsedTombstone = handleProtoTombstone(path, true);
+ Optional<TombstoneFile> parsedTombstone = handleProtoTombstone(protoPath, isProtoFile);
if (parsedTombstone.isPresent()) {
- BootReceiver.addTombstoneToDropBox(
- mContext, path, parsedTombstone.get().getTombstone(),
- parsedTombstone.get().getProcessName(), mTmpFileLock);
+ processName = parsedTombstone.get().getProcessName();
}
+ BootReceiver.addTombstoneToDropBox(mContext, path, isProtoFile, processName, mTmpFileLock);
}
- private Optional<ParsedTombstone> handleProtoTombstone(
- File path, boolean addToList) {
+ private Optional<TombstoneFile> handleProtoTombstone(File path, boolean addToList) {
final String filename = path.getName();
if (!filename.endsWith(".pb")) {
Slog.w(TAG, "unexpected tombstone name: " + path);
@@ -173,7 +171,7 @@ public final class NativeTombstoneManager {
return Optional.empty();
}
- final Optional<ParsedTombstone> parsedTombstone = TombstoneFile.parse(pfd);
+ final Optional<TombstoneFile> parsedTombstone = TombstoneFile.parse(pfd);
if (!parsedTombstone.isPresent()) {
IoUtils.closeQuietly(pfd);
return Optional.empty();
@@ -186,7 +184,7 @@ public final class NativeTombstoneManager {
previous.dispose();
}
- mTombstones.put(number, parsedTombstone.get().getTombstoneFile());
+ mTombstones.put(number, parsedTombstone.get());
}
}
@@ -334,27 +332,6 @@ public final class NativeTombstoneManager {
}
}
- static class ParsedTombstone {
- TombstoneFile mTombstoneFile;
- Tombstone mTombstone;
- ParsedTombstone(TombstoneFile tombstoneFile, Tombstone tombstone) {
- mTombstoneFile = tombstoneFile;
- mTombstone = tombstone;
- }
-
- public String getProcessName() {
- return mTombstoneFile.getProcessName();
- }
-
- public TombstoneFile getTombstoneFile() {
- return mTombstoneFile;
- }
-
- public Tombstone getTombstone() {
- return mTombstone;
- }
- }
-
static class TombstoneFile {
final ParcelFileDescriptor mPfd;
@@ -437,21 +414,67 @@ public final class NativeTombstoneManager {
}
}
- static Optional<ParsedTombstone> parse(ParcelFileDescriptor pfd) {
- Tombstone tombstoneProto;
- try (FileInputStream is = new FileInputStream(pfd.getFileDescriptor())) {
- final byte[] tombstoneBytes = is.readAllBytes();
+ static Optional<TombstoneFile> parse(ParcelFileDescriptor pfd) {
+ final FileInputStream is = new FileInputStream(pfd.getFileDescriptor());
+ final ProtoInputStream stream = new ProtoInputStream(is);
+
+ int pid = 0;
+ int uid = 0;
+ String processName = null;
+ String crashReason = "";
+ String selinuxLabel = "";
+
+ try {
+ while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (stream.getFieldNumber()) {
+ case (int) Tombstone.PID:
+ pid = stream.readInt(Tombstone.PID);
+ break;
+
+ case (int) Tombstone.UID:
+ uid = stream.readInt(Tombstone.UID);
+ break;
+
+ case (int) Tombstone.COMMAND_LINE:
+ if (processName == null) {
+ processName = stream.readString(Tombstone.COMMAND_LINE);
+ }
+ break;
- tombstoneProto = Tombstone.parseFrom(
- CodedInputStream.newInstance(tombstoneBytes));
- } catch (IOException ex) {
+ case (int) Tombstone.CAUSES:
+ if (!crashReason.equals("")) {
+ // Causes appear in decreasing order of likelihood. For now we only
+ // want the most likely crash reason here, so ignore all others.
+ break;
+ }
+ long token = stream.start(Tombstone.CAUSES);
+ cause:
+ while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (stream.getFieldNumber()) {
+ case (int) Cause.HUMAN_READABLE:
+ crashReason = stream.readString(Cause.HUMAN_READABLE);
+ break cause;
+
+ default:
+ break;
+ }
+ }
+ stream.end(token);
+ break;
+
+ case (int) Tombstone.SELINUX_LABEL:
+ selinuxLabel = stream.readString(Tombstone.SELINUX_LABEL);
+ break;
+
+ default:
+ break;
+ }
+ }
+ } catch (IOException | ProtoParseException ex) {
Slog.e(TAG, "Failed to parse tombstone", ex);
return Optional.empty();
}
- int pid = tombstoneProto.getPid();
- int uid = tombstoneProto.getUid();
-
if (!UserHandle.isApp(uid)) {
Slog.e(TAG, "Tombstone's UID (" + uid + ") not an app, ignoring");
return Optional.empty();
@@ -468,7 +491,6 @@ public final class NativeTombstoneManager {
final int userId = UserHandle.getUserId(uid);
final int appId = UserHandle.getAppId(uid);
- String selinuxLabel = tombstoneProto.getSelinuxLabel();
if (!selinuxLabel.startsWith("u:r:untrusted_app")) {
Slog.e(TAG, "Tombstone has invalid selinux label (" + selinuxLabel + "), ignoring");
return Optional.empty();
@@ -480,30 +502,11 @@ public final class NativeTombstoneManager {
result.mAppId = appId;
result.mPid = pid;
result.mUid = uid;
- result.mProcessName = getCmdLineProcessName(tombstoneProto);
+ result.mProcessName = processName == null ? "" : processName;
result.mTimestampMs = timestampMs;
- result.mCrashReason = getCrashReason(tombstoneProto);
+ result.mCrashReason = crashReason;
- return Optional.of(new ParsedTombstone(result, tombstoneProto));
- }
-
- private static String getCmdLineProcessName(Tombstone tombstoneProto) {
- for (String cmdline : tombstoneProto.getCommandLineList()) {
- if (cmdline != null) {
- return cmdline;
- }
- }
- return "";
- }
-
- private static String getCrashReason(Tombstone tombstoneProto) {
- for (Cause cause : tombstoneProto.getCausesList()) {
- if (cause.getHumanReadable() != null
- && !cause.getHumanReadable().equals("")) {
- return cause.getHumanReadable();
- }
- }
- return "";
+ return Optional.of(result);
}
public IParcelFileDescriptorRetriever getPfdRetriever() {
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
index 7f0aadce3143..c110fb67b54f 100644
--- a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -75,11 +75,9 @@ public class BackgroundInstallControlService extends SystemService {
private static final int MSG_PACKAGE_ADDED = 1;
private static final int MSG_PACKAGE_REMOVED = 2;
- private final Context mContext;
private final BinderService mBinderService;
private final PackageManager mPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
- private final UsageStatsManagerInternal mUsageStatsManagerInternal;
private final PermissionManagerServiceInternal mPermissionManager;
private final Handler mHandler;
private final File mDiskFile;
@@ -99,14 +97,14 @@ public class BackgroundInstallControlService extends SystemService {
@VisibleForTesting
BackgroundInstallControlService(@NonNull Injector injector) {
super(injector.getContext());
- mContext = injector.getContext();
mPackageManager = injector.getPackageManager();
mPackageManagerInternal = injector.getPackageManagerInternal();
mPermissionManager = injector.getPermissionManager();
mHandler = new EventHandler(injector.getLooper(), this);
mDiskFile = injector.getDiskFile();
- mUsageStatsManagerInternal = injector.getUsageStatsManagerInternal();
- mUsageStatsManagerInternal.registerListener(
+ UsageStatsManagerInternal usageStatsManagerInternal =
+ injector.getUsageStatsManagerInternal();
+ usageStatsManagerInternal.registerListener(
(userId, event) ->
mHandler.obtainMessage(MSG_USAGE_EVENT_RECEIVED,
userId,
diff --git a/services/core/java/com/android/server/pm/DataLoaderManagerService.java b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
index 888e1c26206c..c25cea65376b 100644
--- a/services/core/java/com/android/server/pm/DataLoaderManagerService.java
+++ b/services/core/java/com/android/server/pm/DataLoaderManagerService.java
@@ -47,7 +47,6 @@ import java.util.List;
public class DataLoaderManagerService extends SystemService {
private static final String TAG = "DataLoaderManager";
private final Context mContext;
- private final HandlerThread mThread;
private final Handler mHandler;
private final DataLoaderManagerBinderService mBinderService;
private final SparseArray<DataLoaderServiceConnection> mServiceConnections =
@@ -57,10 +56,10 @@ public class DataLoaderManagerService extends SystemService {
super(context);
mContext = context;
- mThread = new HandlerThread(TAG);
- mThread.start();
+ HandlerThread thread = new HandlerThread(TAG);
+ thread.start();
- mHandler = new Handler(mThread.getLooper());
+ mHandler = new Handler(thread.getLooper());
mBinderService = new DataLoaderManagerBinderService();
}
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index e3bbd2d8d17e..f987d4ae8999 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -107,9 +107,6 @@ public abstract class IPackageManagerBase extends IPackageManager.Stub {
@Nullable
private final ComponentName mInstantAppResolverSettingsComponent;
- @NonNull
- private final String mRequiredSupplementalProcessPackage;
-
@Nullable
private final String mServicesExtensionPackageName;
@@ -125,7 +122,6 @@ public abstract class IPackageManagerBase extends IPackageManager.Stub {
@NonNull PackageInstallerService installerService,
@NonNull PackageProperty packageProperty, @NonNull ComponentName resolveComponentName,
@Nullable ComponentName instantAppResolverSettingsComponent,
- @NonNull String requiredSupplementalProcessPackage,
@Nullable String servicesExtensionPackageName,
@Nullable String sharedSystemSharedLibraryPackageName) {
mService = service;
@@ -140,7 +136,6 @@ public abstract class IPackageManagerBase extends IPackageManager.Stub {
mPackageProperty = packageProperty;
mResolveComponentName = resolveComponentName;
mInstantAppResolverSettingsComponent = instantAppResolverSettingsComponent;
- mRequiredSupplementalProcessPackage = requiredSupplementalProcessPackage;
mServicesExtensionPackageName = servicesExtensionPackageName;
mSharedSystemSharedLibraryPackageName = sharedSystemSharedLibraryPackageName;
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 991555495ad2..ac826afc1d22 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -32,6 +32,8 @@ import static android.content.pm.LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS;
import static android.content.pm.LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS;
import static android.content.pm.LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS;
+import static com.android.server.pm.PackageArchiver.isArchivingEnabled;
+
import android.annotation.AppIdInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -56,7 +58,6 @@ import android.content.IntentSender;
import android.content.LocusId;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
-import android.content.pm.Flags;
import android.content.pm.ILauncherApps;
import android.content.pm.IOnAppsChangedListener;
import android.content.pm.IPackageInstallerCallback;
@@ -507,7 +508,8 @@ public class LauncherAppsService extends SystemService {
if (!canAccessProfile(userId, "cannot get shouldHideFromSuggestions")) {
return false;
}
- if (Flags.archiving() && packageName != null && isPackageArchived(packageName, user)) {
+ if (isArchivingEnabled() && packageName != null
+ && isPackageArchived(packageName, user)) {
return true;
}
if (mPackageManagerInternal.filterAppAccess(
@@ -530,7 +532,7 @@ public class LauncherAppsService extends SystemService {
.addCategory(Intent.CATEGORY_LAUNCHER)
.setPackage(packageName),
user);
- if (Flags.archiving()) {
+ if (isArchivingEnabled()) {
launcherActivities =
getActivitiesForArchivedApp(packageName, user, launcherActivities);
}
@@ -701,7 +703,7 @@ public class LauncherAppsService extends SystemService {
callingUid,
user.getIdentifier());
if (activityInfo == null) {
- if (Flags.archiving()) {
+ if (isArchivingEnabled()) {
return getMatchingArchivedAppActivityInfo(component, user);
}
return null;
@@ -984,7 +986,7 @@ public class LauncherAppsService extends SystemService {
long callingFlag =
PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
- if (Flags.archiving()) {
+ if (isArchivingEnabled()) {
callingFlag |= PackageManager.MATCH_ARCHIVED_PACKAGES;
}
final PackageInfo info =
@@ -1457,7 +1459,7 @@ public class LauncherAppsService extends SystemService {
if (!canAccessProfile(user.getIdentifier(), "Cannot check component")) {
return false;
}
- if (Flags.archiving() && component != null && component.getPackageName() != null) {
+ if (isArchivingEnabled() && component != null && component.getPackageName() != null) {
List<LauncherActivityInfoInternal> archiveActivities =
generateLauncherActivitiesForArchivedApp(component.getPackageName(), user);
if (!archiveActivities.isEmpty()) {
@@ -1788,7 +1790,7 @@ public class LauncherAppsService extends SystemService {
}
if (!canLaunch
&& includeArchivedApps
- && Flags.archiving()
+ && isArchivingEnabled()
&& getMatchingArchivedAppActivityInfo(component, user) != null) {
launchIntent.setPackage(null);
launchIntent.setComponent(component);
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index 3e5759a88213..09a91eda483a 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -51,12 +51,13 @@ import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.ArchivedActivityParcel;
+import android.content.pm.ArchivedPackageInfo;
import android.content.pm.ArchivedPackageParcel;
+import android.content.pm.Flags;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.DeleteFlags;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.VersionedPackage;
@@ -77,6 +78,7 @@ import android.os.ParcelableException;
import android.os.Process;
import android.os.RemoteException;
import android.os.SELinux;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ExceptionUtils;
@@ -172,12 +174,15 @@ public class PackageArchiver {
return userState.getArchiveState() != null && !userState.isInstalled();
}
+ public static boolean isArchivingEnabled() {
+ return Flags.archiving() || SystemProperties.getBoolean("pm.archiving.enabled", false);
+ }
+
void requestArchive(
@NonNull String packageName,
@NonNull String callerPackageName,
@NonNull IntentSender intentSender,
- @NonNull UserHandle userHandle,
- @DeleteFlags int flags) {
+ @NonNull UserHandle userHandle) {
Objects.requireNonNull(packageName);
Objects.requireNonNull(callerPackageName);
Objects.requireNonNull(intentSender);
@@ -217,7 +222,7 @@ public class PackageArchiver {
new VersionedPackage(packageName,
PackageManager.VERSION_CODE_HIGHEST),
callerPackageName,
- DELETE_ARCHIVE | DELETE_KEEP_DATA | flags,
+ DELETE_ARCHIVE | DELETE_KEEP_DATA,
intentSender,
userId,
binderUid);
@@ -402,23 +407,30 @@ public class PackageArchiver {
installerPackage, /* flags= */ 0, userId);
if (installerInfo == null) {
// Should never happen because we just fetched the installerInfo.
- Slog.e(TAG, "Couldnt find installer " + installerPackage);
+ Slog.e(TAG, "Couldn't find installer " + installerPackage);
return null;
}
+ final int iconSize = mContext.getSystemService(
+ ActivityManager.class).getLauncherLargeIconSize();
+
+ var info = new ArchivedPackageInfo(archivedPackage);
try {
- var packageName = archivedPackage.packageName;
- var mainActivities = archivedPackage.archivedActivities;
- List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.length);
- for (int i = 0, size = mainActivities.length; i < size; ++i) {
- var mainActivity = mainActivities[i];
- Path iconPath = storeIconForParcel(packageName, mainActivity, userId, i);
+ var packageName = info.getPackageName();
+ var mainActivities = info.getLauncherActivities();
+ List<ArchiveActivityInfo> archiveActivityInfos = new ArrayList<>(mainActivities.size());
+ for (int i = 0, size = mainActivities.size(); i < size; ++i) {
+ var mainActivity = mainActivities.get(i);
+ Path iconPath = storeDrawable(
+ packageName, mainActivity.getIcon(), userId, i, iconSize);
+ Path monochromePath = storeDrawable(
+ packageName, mainActivity.getMonochromeIcon(), userId, i, iconSize);
ArchiveActivityInfo activityInfo =
new ArchiveActivityInfo(
- mainActivity.title,
- mainActivity.originalComponentName,
+ mainActivity.getLabel().toString(),
+ mainActivity.getComponentName(),
iconPath,
- null);
+ monochromePath);
archiveActivityInfos.add(activityInfo);
}
@@ -452,21 +464,6 @@ public class PackageArchiver {
return new ArchiveState(archiveActivityInfos, installerTitle);
}
- // TODO(b/298452477) Handle monochrome icons.
- private static Path storeIconForParcel(String packageName, ArchivedActivityParcel mainActivity,
- @UserIdInt int userId, int index) throws IOException {
- if (mainActivity.iconBitmap == null) {
- return null;
- }
- File iconsDir = createIconsDir(packageName, userId);
- File iconFile = new File(iconsDir, index + ".png");
- try (FileOutputStream out = new FileOutputStream(iconFile)) {
- out.write(mainActivity.iconBitmap);
- out.flush();
- }
- return iconFile.toPath();
- }
-
@VisibleForTesting
Path storeIcon(String packageName, LauncherActivityInfo mainActivity,
@UserIdInt int userId, int index, int iconSize) throws IOException {
@@ -475,9 +472,18 @@ public class PackageArchiver {
// The app doesn't define an icon. No need to store anything.
return null;
}
+ return storeDrawable(packageName, mainActivity.getIcon(/* density= */ 0), userId, index,
+ iconSize);
+ }
+
+ private static Path storeDrawable(String packageName, @Nullable Drawable iconDrawable,
+ @UserIdInt int userId, int index, int iconSize) throws IOException {
+ if (iconDrawable == null) {
+ return null;
+ }
File iconsDir = createIconsDir(packageName, userId);
File iconFile = new File(iconsDir, index + ".png");
- Bitmap icon = drawableToBitmap(mainActivity.getIcon(/* density= */ 0), iconSize);
+ Bitmap icon = drawableToBitmap(iconDrawable, iconSize);
try (FileOutputStream out = new FileOutputStream(iconFile)) {
// Note: Quality is ignored for PNGs.
if (!icon.compress(Bitmap.CompressFormat.PNG, /* quality= */ 100, out)) {
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index fdcd28b0ed02..7bf9fe7aa7e2 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -30,6 +30,7 @@ import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT;
import static android.os.Process.INVALID_UID;
import static android.os.Process.SYSTEM_UID;
+import static com.android.server.pm.PackageArchiver.isArchivingEnabled;
import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -826,7 +827,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
}
params.installFlags &= ~PackageManager.INSTALL_UNARCHIVE;
- if (Flags.archiving() && params.appPackageName != null) {
+ if (isArchivingEnabled() && params.appPackageName != null) {
PackageStateInternal ps = mPm.snapshotComputer().getPackageStateInternal(
params.appPackageName, SYSTEM_UID);
if (ps != null
@@ -1034,7 +1035,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
private int getExistingDraftSessionIdInternal(int installerUid,
SessionParams sessionParams, int userId) {
String appPackageName = sessionParams.appPackageName;
- if (!Flags.archiving() || installerUid == INVALID_UID || appPackageName == null) {
+ if (!isArchivingEnabled() || installerUid == INVALID_UID || appPackageName == null) {
return SessionInfo.INVALID_ID;
}
@@ -1407,14 +1408,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
final PackageDeleteObserverAdapter adapter = new PackageDeleteObserverAdapter(mContext,
statusReceiver, versionedPackage.getPackageName(),
canSilentlyInstallPackage, userId, mPackageArchiver, flags);
- final boolean shouldShowConfirmationDialog =
- (flags & PackageManager.DELETE_SHOW_DIALOG) != 0;
- if (!shouldShowConfirmationDialog
- && mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES)
- == PackageManager.PERMISSION_GRANTED) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.DELETE_PACKAGES)
+ == PackageManager.PERMISSION_GRANTED) {
// Sweet, call straight through!
mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags);
- } else if (!shouldShowConfirmationDialog && canSilentlyInstallPackage) {
+ } else if (canSilentlyInstallPackage) {
// Allow the device owner and affiliated profile owner to silently delete packages
// Need to clear the calling identity to get DELETE_PACKAGES permission
final long ident = Binder.clearCallingIdentity();
@@ -1656,10 +1654,8 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
@NonNull String packageName,
@NonNull String callerPackageName,
@NonNull IntentSender intentSender,
- @NonNull UserHandle userHandle,
- @DeleteFlags int flags) {
- mPackageArchiver.requestArchive(packageName, callerPackageName, intentSender,
- userHandle, flags);
+ @NonNull UserHandle userHandle) {
+ mPackageArchiver.requestArchive(packageName, callerPackageName, intentSender, userHandle);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 5225529ef001..c5b5a761497d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4659,8 +4659,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mPreferredActivityHelper, mResolveIntentHelper, mDomainVerificationManager,
mDomainVerificationConnection, mInstallerService, mPackageProperty,
mResolveComponentName, mInstantAppResolverSettingsComponent,
- mRequiredSdkSandboxPackage, mServicesExtensionPackageName,
- mSharedSystemSharedLibraryPackageName);
+ mServicesExtensionPackageName, mSharedSystemSharedLibraryPackageName);
}
@Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 5724ee0d94e9..ca00c84da724 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -4645,7 +4645,7 @@ class PackageManagerShellCommand extends ShellCommand {
try {
mInterface.getPackageInstaller().requestArchive(packageName,
/* callerPackageName= */ "", receiver.getIntentSender(),
- new UserHandle(translatedUserId), 0);
+ new UserHandle(translatedUserId));
} catch (Exception e) {
pw.println("Failure [" + e.getMessage() + "]");
return 1;
diff --git a/services/core/java/com/android/server/pm/ProtectedPackages.java b/services/core/java/com/android/server/pm/ProtectedPackages.java
index 98533725371f..524252c1469f 100644
--- a/services/core/java/com/android/server/pm/ProtectedPackages.java
+++ b/services/core/java/com/android/server/pm/ProtectedPackages.java
@@ -57,11 +57,8 @@ public class ProtectedPackages {
@GuardedBy("this")
private final SparseArray<Set<String>> mOwnerProtectedPackages = new SparseArray<>();
- private final Context mContext;
-
public ProtectedPackages(Context context) {
- mContext = context;
- mDeviceProvisioningPackage = mContext.getResources().getString(
+ mDeviceProvisioningPackage = context.getResources().getString(
R.string.config_deviceProvisioningPackage);
}
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 7bd6a43969ba..70352be01096 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -67,7 +67,6 @@ final class RemovePackageHelper {
private final PackageManagerService mPm;
private final IncrementalManager mIncrementalManager;
private final Installer mInstaller;
- private final UserManagerInternal mUserManagerInternal;
private final PermissionManagerServiceInternal mPermissionManager;
private final SharedLibrariesImpl mSharedLibraries;
private final AppDataHelper mAppDataHelper;
@@ -79,7 +78,6 @@ final class RemovePackageHelper {
mPm = pm;
mIncrementalManager = mPm.mInjector.getIncrementalManager();
mInstaller = mPm.mInjector.getInstaller();
- mUserManagerInternal = mPm.mInjector.getUserManagerInternal();
mPermissionManager = mPm.mInjector.getPermissionManagerServiceInternal();
mSharedLibraries = mPm.mInjector.getSharedLibrariesImpl();
mAppDataHelper = appDataHelper;
@@ -394,8 +392,7 @@ final class RemovePackageHelper {
// Delete from mSettings
final SparseBooleanArray changedUsers = new SparseBooleanArray();
synchronized (mPm.mLock) {
- mPm.mSettings.removePackageLPw(packageName);
- outInfo.mIsAppIdRemoved = true;
+ outInfo.mIsAppIdRemoved = mPm.mSettings.removePackageAndAppIdLPw(packageName);
if (!mPm.mSettings.isDisabledSystemPackageLPr(packageName)) {
final SharedUserSetting sus = mPm.mSettings.getSharedUserSettingLPr(deletedPs);
// If we don't have a disabled system package to reinstall, the package is
@@ -487,8 +484,6 @@ final class RemovePackageHelper {
synchronized (mPm.mInstallLock) {
cleanUpResourcesLI(codeFile, instructionSets);
}
- // TODO: open logging to help debug, will delete or add debug flag
- Slog.d(TAG, "cleanUpResources for " + codeFile);
if (packageName == null) {
return;
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index b286b12dcf7d..a97652c8e167 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -92,7 +92,6 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
-import com.android.internal.pm.parsing.pkg.PackageImpl;
import com.android.internal.pm.pkg.component.ParsedComponent;
import com.android.internal.pm.pkg.component.ParsedIntentInfo;
import com.android.internal.pm.pkg.component.ParsedPermission;
@@ -1460,22 +1459,28 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile
return false;
}
- int removePackageLPw(String name) {
+
+ /**
+ * Remove package from mPackages and its corresponding AppId.
+ *
+ * @return True if the AppId has been removed.
+ * False if the app doesn't exist, or if the app has a shared UID and there are other apps that
+ * still use the same shared UID even after the target app is removed.
+ */
+ boolean removePackageAndAppIdLPw(String name) {
final PackageSetting p = mPackages.remove(name);
if (p != null) {
removeInstallerPackageStatus(name);
SharedUserSetting sharedUserSetting = getSharedUserSettingLPr(p);
if (sharedUserSetting != null) {
sharedUserSetting.removePackage(p);
- if (checkAndPruneSharedUserLPw(sharedUserSetting, false)) {
- return sharedUserSetting.mAppId;
- }
+ return checkAndPruneSharedUserLPw(sharedUserSetting, false);
} else {
removeAppIdLPw(p.getAppId());
- return p.getAppId();
+ return true;
}
}
- return -1;
+ return false;
}
/**
diff --git a/services/core/java/com/android/server/pm/ShortcutNonPersistentUser.java b/services/core/java/com/android/server/pm/ShortcutNonPersistentUser.java
index 7f6f684e0b68..aa52522cfe46 100644
--- a/services/core/java/com/android/server/pm/ShortcutNonPersistentUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutNonPersistentUser.java
@@ -31,7 +31,6 @@ import java.io.PrintWriter;
* The access to it must be guarded with the shortcut manager lock.
*/
public class ShortcutNonPersistentUser {
- private final ShortcutService mService;
private final int mUserId;
@@ -49,8 +48,7 @@ public class ShortcutNonPersistentUser {
*/
private final ArraySet<String> mHostPackageSet = new ArraySet<>();
- public ShortcutNonPersistentUser(ShortcutService service, int userId) {
- mService = service;
+ public ShortcutNonPersistentUser(int userId) {
mUserId = userId;
}
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index e993d9e5b724..d644235b8714 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -33,6 +33,7 @@ import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaRequest;
+import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -47,6 +48,7 @@ import android.graphics.drawable.Icon;
import android.os.Binder;
import android.os.PersistableBundle;
import android.os.StrictMode;
+import android.os.SystemClock;
import android.text.format.Formatter;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -192,6 +194,9 @@ class ShortcutPackage extends ShortcutPackageItem {
private long mLastKnownForegroundElapsedTime;
@GuardedBy("mLock")
+ private long mLastReportedTime;
+
+ @GuardedBy("mLock")
private boolean mIsAppSearchSchemaUpToDate;
private ShortcutPackage(ShortcutUser shortcutUser,
@@ -1673,6 +1678,26 @@ class ShortcutPackage extends ShortcutPackageItem {
return condition[0];
}
+ void reportShortcutUsed(@NonNull final UsageStatsManagerInternal usageStatsManagerInternal,
+ @NonNull final String shortcutId) {
+ synchronized (mLock) {
+ final long currentTS = SystemClock.elapsedRealtime();
+ final ShortcutService s = mShortcutUser.mService;
+ if (currentTS - mLastReportedTime > s.mSaveDelayMillis) {
+ mLastReportedTime = currentTS;
+ } else {
+ return;
+ }
+ final long token = s.injectClearCallingIdentity();
+ try {
+ usageStatsManagerInternal.reportShortcutUsage(getPackageName(), shortcutId,
+ getPackageUserId());
+ } finally {
+ s.injectRestoreCallingIdentity(token);
+ }
+ }
+ }
+
public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
pw.println();
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 446c6293aa35..c23d2abf0853 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -371,7 +371,7 @@ public class ShortcutService extends IShortcutService.Stub {
private CompressFormat mIconPersistFormat;
private int mIconPersistQuality;
- private int mSaveDelayMillis;
+ int mSaveDelayMillis;
private final IPackageManager mIPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
@@ -1378,7 +1378,7 @@ public class ShortcutService extends IShortcutService.Stub {
ShortcutNonPersistentUser getNonPersistentUserLocked(@UserIdInt int userId) {
ShortcutNonPersistentUser ret = mShortcutNonPersistentUsers.get(userId);
if (ret == null) {
- ret = new ShortcutNonPersistentUser(this, userId);
+ ret = new ShortcutNonPersistentUser(userId);
mShortcutNonPersistentUsers.put(userId, ret);
}
return ret;
@@ -2291,7 +2291,7 @@ public class ShortcutService extends IShortcutService.Stub {
packageShortcutsChanged(ps, changedShortcuts, removedShortcuts);
- reportShortcutUsedInternal(packageName, shortcut.getId(), userId);
+ ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcut.getId());
verifyStates();
}
@@ -2695,25 +2695,17 @@ public class ShortcutService extends IShortcutService.Stub {
Slog.d(TAG, String.format("reportShortcutUsed: Shortcut %s package %s used on user %d",
shortcutId, packageName, userId));
}
+ final ShortcutPackage ps;
synchronized (mLock) {
throwIfUserLockedL(userId);
- final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(packageName, userId);
+ ps = getPackageShortcutsForPublisherLocked(packageName, userId);
if (ps.findShortcutById(shortcutId) == null) {
Log.w(TAG, String.format("reportShortcutUsed: package %s doesn't have shortcut %s",
packageName, shortcutId));
return;
}
}
- reportShortcutUsedInternal(packageName, shortcutId, userId);
- }
-
- private void reportShortcutUsedInternal(String packageName, String shortcutId, int userId) {
- final long token = injectClearCallingIdentity();
- try {
- mUsageStatsManagerInternal.reportShortcutUsage(packageName, shortcutId, userId);
- } finally {
- injectRestoreCallingIdentity(token);
- }
+ ps.reportShortcutUsed(mUsageStatsManagerInternal, shortcutId);
}
@Override
@@ -5202,13 +5194,11 @@ public class ShortcutService extends IShortcutService.Stub {
}
// Injection point.
- @VisibleForTesting
long injectClearCallingIdentity() {
return Binder.clearCallingIdentity();
}
// Injection point.
- @VisibleForTesting
void injectRestoreCallingIdentity(long token) {
Binder.restoreCallingIdentity(token);
}
diff --git a/services/core/java/com/android/server/pm/UserDataPreparer.java b/services/core/java/com/android/server/pm/UserDataPreparer.java
index 4c42c2dd0850..1d414011cff3 100644
--- a/services/core/java/com/android/server/pm/UserDataPreparer.java
+++ b/services/core/java/com/android/server/pm/UserDataPreparer.java
@@ -141,7 +141,7 @@ class UserDataPreparer {
// If internal storage of the system user fails to prepare on first boot, then
// things are *really* broken, so we might as well reboot to recovery right away.
try {
- Log.wtf(TAG, "prepareUserData failed for user " + userId, e);
+ Log.e(TAG, "prepareUserData failed for user " + userId, e);
if (isNewUser && userId == UserHandle.USER_SYSTEM && volumeUuid == null) {
RecoverySystem.rebootPromptAndWipeUserData(mContext,
"failed to prepare internal storage for system user");
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c1b74898e5ae..c94111c31ef4 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -16,6 +16,8 @@
package com.android.server.pm;
+import static android.content.Intent.ACTION_SCREEN_OFF;
+import static android.content.Intent.ACTION_SCREEN_ON;
import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.UserManager.DEV_CREATE_OVERRIDE_PROPERTY;
@@ -41,11 +43,13 @@ import android.annotation.ColorRes;
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.StringRes;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityManagerNative;
+import android.app.ActivityOptions;
import android.app.BroadcastOptions;
import android.app.IActivityManager;
import android.app.IStopUserCallback;
@@ -73,8 +77,10 @@ import android.content.pm.UserProperties;
import android.content.pm.parsing.FrameworkParsingPackageUtils;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.database.ContentObserver;
import android.graphics.Bitmap;
import android.multiuser.Flags;
+import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -82,6 +88,7 @@ import android.os.Debug;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.IProgressListener;
import android.os.IUserManager;
@@ -90,6 +97,7 @@ import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
import android.os.PersistableBundle;
+import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -173,6 +181,8 @@ import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@@ -295,6 +305,12 @@ public class UserManagerService extends IUserManager.Stub {
private static final long BOOT_USER_SET_TIMEOUT_MS = 300_000;
+ /**
+ * The time duration (in milliseconds) post device inactivity after which the private space
+ * should be auto-locked if the corresponding settings option is selected by the user.
+ */
+ private static final long PRIVATE_SPACE_AUTO_LOCK_INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000;
+
// Tron counters
private static final String TRON_GUEST_CREATED = "users_guest_created";
private static final String TRON_USER_CREATED = "users_user_created";
@@ -320,6 +336,8 @@ public class UserManagerService extends IUserManager.Stub {
private final Handler mHandler;
+ private final ThreadPoolExecutor mInternalExecutor;
+
private final File mUsersDir;
private final File mUserListFile;
@@ -521,6 +539,36 @@ public class UserManagerService extends IUserManager.Stub {
private final LockPatternUtils mLockPatternUtils;
+ private KeyguardManager.KeyguardLockedStateListener mKeyguardLockedStateListener;
+
+ /** Token to identify and remove already scheduled private space auto-lock messages */
+ private static final Object PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN = new Object();
+
+ /** Content observer to get callbacks for privte space autolock settings changes */
+ private final SettingsObserver mPrivateSpaceAutoLockSettingsObserver;
+
+ private final class SettingsObserver extends ContentObserver {
+ SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ if (isAutoLockForPrivateSpaceEnabled()) {
+ final String path = uri.getLastPathSegment();
+ if (TextUtils.equals(path, Settings.Secure.PRIVATE_SPACE_AUTO_LOCK)) {
+ int autoLockPreference =
+ Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+ getMainUserIdUnchecked());
+ Slog.i(LOG_TAG, "Auto-lock settings changed to " + autoLockPreference);
+ setOrUpdateAutoLockPreferenceForPrivateProfile(autoLockPreference);
+ }
+ }
+ }
+ }
+
private final String ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK =
"com.android.server.pm.DISABLE_QUIET_MODE_AFTER_UNLOCK";
@@ -533,12 +581,168 @@ public class UserManagerService extends IUserManager.Stub {
final IntentSender target = intent.getParcelableExtra(Intent.EXTRA_INTENT, android.content.IntentSender.class);
final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
final String callingPackage = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
- // Call setQuietModeEnabled on bg thread to avoid ANR
- BackgroundThread.getHandler().post(() ->
- setQuietModeEnabled(userId, false, target, callingPackage));
+ setQuietModeEnabledAsync(userId, false, target, callingPackage);
}
};
+ /** Checks if the device inactivity broadcast receiver is already registered*/
+ private boolean mIsDeviceInactivityBroadcastReceiverRegistered = false;
+
+ private final BroadcastReceiver mDeviceInactivityBroadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (isAutoLockForPrivateSpaceEnabled()) {
+ if (ACTION_SCREEN_OFF.equals(intent.getAction())) {
+ maybeScheduleMessageToAutoLockPrivateSpace();
+ } else if (ACTION_SCREEN_ON.equals(intent.getAction())) {
+ // Remove any queued messages since the device is interactive again
+ mHandler.removeCallbacksAndMessages(PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN);
+ }
+ }
+ }
+ };
+
+ @VisibleForTesting
+ void maybeScheduleMessageToAutoLockPrivateSpace() {
+ // No action needed if auto-lock on inactivity not selected
+ int privateSpaceAutoLockPreference =
+ Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+ getMainUserIdUnchecked());
+ if (privateSpaceAutoLockPreference
+ != Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY) {
+ return;
+ }
+ int privateProfileUserId = getPrivateProfileUserId();
+ if (privateProfileUserId != UserHandle.USER_NULL) {
+ scheduleMessageToAutoLockPrivateSpace(privateProfileUserId,
+ PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN,
+ PRIVATE_SPACE_AUTO_LOCK_INACTIVITY_TIMEOUT_MS);
+ }
+ }
+
+ @VisibleForTesting
+ void scheduleMessageToAutoLockPrivateSpace(int userId, Object token,
+ long delayInMillis) {
+ mHandler.postDelayed(() -> {
+ final PowerManager powerManager = mContext.getSystemService(PowerManager.class);
+ if (powerManager != null && !powerManager.isInteractive()) {
+ Slog.i(LOG_TAG, "Auto-locking private space with user-id " + userId);
+ setQuietModeEnabledAsync(userId, true,
+ /* target */ null, mContext.getPackageName());
+ } else {
+ Slog.i(LOG_TAG, "Device is interactive, skipping auto-lock");
+ }
+ }, token, delayInMillis);
+ }
+
+ @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
+ private void initializeAndRegisterKeyguardLockedStateListener() {
+ mKeyguardLockedStateListener = this::tryAutoLockingPrivateSpaceOnKeyguardChanged;
+ // Register with keyguard to send locked state events to the listener initialized above
+ try {
+ final KeyguardManager keyguardManager =
+ mContext.getSystemService(KeyguardManager.class);
+ Slog.i(LOG_TAG, "Adding keyguard locked state listener");
+ keyguardManager.addKeyguardLockedStateListener(new HandlerExecutor(mHandler),
+ mKeyguardLockedStateListener);
+ } catch (Exception e) {
+ Slog.e(LOG_TAG, "Error adding keyguard locked listener ", e);
+ }
+ }
+
+ @VisibleForTesting
+ @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE)
+ void setOrUpdateAutoLockPreferenceForPrivateProfile(
+ @Settings.Secure.PrivateSpaceAutoLockOption int autoLockPreference) {
+ int privateProfileUserId = getPrivateProfileUserId();
+ if (privateProfileUserId == UserHandle.USER_NULL) {
+ Slog.e(LOG_TAG, "Auto-lock preference updated but private space user not found");
+ return;
+ }
+
+ if (autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY) {
+ // Register inactivity broadcast
+ if (!mIsDeviceInactivityBroadcastReceiverRegistered) {
+ Slog.i(LOG_TAG, "Registering device inactivity broadcast receivers");
+ mContext.registerReceiver(mDeviceInactivityBroadcastReceiver,
+ new IntentFilter(ACTION_SCREEN_OFF),
+ null, mHandler);
+
+ mContext.registerReceiver(mDeviceInactivityBroadcastReceiver,
+ new IntentFilter(ACTION_SCREEN_ON),
+ null, mHandler);
+
+ mIsDeviceInactivityBroadcastReceiverRegistered = true;
+ }
+ } else {
+ // Unregister device inactivity broadcasts
+ if (mIsDeviceInactivityBroadcastReceiverRegistered) {
+ Slog.i(LOG_TAG, "Removing device inactivity broadcast receivers");
+ mHandler.removeCallbacksAndMessages(PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN);
+ mContext.unregisterReceiver(mDeviceInactivityBroadcastReceiver);
+ mIsDeviceInactivityBroadcastReceiverRegistered = false;
+ }
+ }
+
+ if (autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK) {
+ // Initialize and add keyguard state listener
+ initializeAndRegisterKeyguardLockedStateListener();
+ } else {
+ // Remove keyguard state listener
+ try {
+ final KeyguardManager keyguardManager =
+ mContext.getSystemService(KeyguardManager.class);
+ Slog.i(LOG_TAG, "Removing keyguard locked state listener");
+ keyguardManager.removeKeyguardLockedStateListener(mKeyguardLockedStateListener);
+ } catch (Exception e) {
+ Slog.e(LOG_TAG, "Error adding keyguard locked state listener ", e);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void tryAutoLockingPrivateSpaceOnKeyguardChanged(boolean isKeyguardLocked) {
+ if (isAutoLockForPrivateSpaceEnabled()) {
+ int autoLockPreference = Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER,
+ getMainUserIdUnchecked());
+ boolean isAutoLockOnDeviceLockSelected =
+ autoLockPreference == Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK;
+ if (isKeyguardLocked && isAutoLockOnDeviceLockSelected) {
+ int privateProfileUserId = getPrivateProfileUserId();
+ if (privateProfileUserId != UserHandle.USER_NULL) {
+ Slog.i(LOG_TAG, "Auto-locking private space with user-id "
+ + privateProfileUserId);
+ setQuietModeEnabledAsync(privateProfileUserId,
+ /* enableQuietMode */true, /* target */ null,
+ mContext.getPackageName());
+ }
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void setQuietModeEnabledAsync(@UserIdInt int userId, boolean enableQuietMode,
+ IntentSender target, @Nullable String callingPackage) {
+ if (android.multiuser.Flags.moveQuietModeOperationsToSeparateThread()) {
+ // Call setQuietModeEnabled on a separate thread. Calling this operation on the main
+ // thread can cause ANRs, posting on a BackgroundThread can result in delays
+ Slog.d(LOG_TAG, "Calling setQuietModeEnabled for user " + userId
+ + " on a separate thread");
+ mInternalExecutor.execute(() -> setQuietModeEnabled(userId, enableQuietMode, target,
+ callingPackage));
+ } else {
+ // Call setQuietModeEnabled on bg thread to avoid ANR
+ BackgroundThread.getHandler().post(
+ () -> setQuietModeEnabled(userId, enableQuietMode, target,
+ callingPackage)
+ );
+ }
+ }
+
/**
* Cache the owner name string, since it could be read repeatedly on a critical code path
* but hit by slow IO. This could be eliminated once we have the cached UserInfo in place.
@@ -587,7 +791,10 @@ public class UserManagerService extends IUserManager.Stub {
public void onFinished(int id, Bundle extras) {
mHandler.post(() -> {
try {
- mContext.startIntentSender(mTarget, null, 0, 0, 0);
+ ActivityOptions activityOptions =
+ ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+ mContext.startIntentSender(mTarget, null, 0, 0, 0, activityOptions.toBundle());
} catch (IntentSender.SendIntentException e) {
Slog.e(LOG_TAG, "Failed to start the target in the callback", e);
}
@@ -762,6 +969,8 @@ public class UserManagerService extends IUserManager.Stub {
mPackagesLock = packagesLock;
mUsers = users != null ? users : new SparseArray<>();
mHandler = new MainHandler();
+ mInternalExecutor = new ThreadPoolExecutor(/* corePoolSize */ 0, /* maximumPoolSize */ 1,
+ /* keepAliveTime */ 1, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
mUserVisibilityMediator = new UserVisibilityMediator(mHandler);
mUserDataPreparer = userDataPreparer;
mUserTypes = UserTypeFactory.getUserTypes();
@@ -786,9 +995,15 @@ public class UserManagerService extends IUserManager.Stub {
mLockPatternUtils = new LockPatternUtils(mContext);
mUserStates.put(UserHandle.USER_SYSTEM, UserState.STATE_BOOTING);
mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null;
+ mPrivateSpaceAutoLockSettingsObserver = new SettingsObserver(mHandler);
emulateSystemUserModeIfNeeded();
}
+ private static boolean isAutoLockForPrivateSpaceEnabled() {
+ return android.os.Flags.allowPrivateProfile()
+ && Flags.supportAutolockForPrivateSpace();
+ }
+
void systemReady() {
mAppOpsService = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
@@ -805,6 +1020,22 @@ public class UserManagerService extends IUserManager.Stub {
new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED),
null, mHandler);
+ if (isAutoLockForPrivateSpaceEnabled()) {
+
+ int mainUserId = getMainUserIdUnchecked();
+ if (mainUserId != UserHandle.USER_NULL) {
+ mContext.getContentResolver().registerContentObserverAsUser(
+ Settings.Secure.getUriFor(
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK), false,
+ mPrivateSpaceAutoLockSettingsObserver, UserHandle.of(mainUserId));
+
+ setOrUpdateAutoLockPreferenceForPrivateProfile(
+ Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK,
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER, mainUserId));
+ }
+ }
+
markEphemeralUsersForRemoval();
}
@@ -969,6 +1200,18 @@ public class UserManagerService extends IUserManager.Stub {
return UserHandle.USER_NULL;
}
+ private @UserIdInt int getPrivateProfileUserId() {
+ synchronized (mUsersLock) {
+ for (int userId : getUserIds()) {
+ UserInfo userInfo = getUserInfoLU(userId);
+ if (userInfo != null && userInfo.isPrivateProfile()) {
+ return userInfo.id;
+ }
+ }
+ }
+ return UserHandle.USER_NULL;
+ }
+
@Override
public void setBootUser(@UserIdInt int userId) {
checkCreateUsersPermission("Set boot user");
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index ee40e18b7d9f..40f226435194 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -77,7 +77,6 @@ import com.android.internal.util.Preconditions;
import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.TriFunction;
import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerService;
import com.android.server.pm.permission.PermissionManagerServiceInternal.HotwordDetectionServiceProvider;
import com.android.server.pm.pkg.AndroidPackage;
@@ -114,9 +113,6 @@ public class PermissionManagerService extends IPermissionManager.Stub {
/** Internal connection to the package manager */
private final PackageManagerInternal mPackageManagerInt;
- /** Internal connection to the user manager */
- private final UserManagerInternal mUserManagerInt;
-
/** Map of OneTimePermissionUserManagers keyed by userId */
@GuardedBy("mLock")
@NonNull
@@ -148,7 +144,6 @@ public class PermissionManagerService extends IPermissionManager.Stub {
mContext = context;
mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
- mUserManagerInt = LocalServices.getService(UserManagerInternal.class);
mAppOpsManager = context.getSystemService(AppOpsManager.class);
mAttributionSourceRegistry = new AttributionSourceRegistry(context);
@@ -1098,12 +1093,10 @@ public class PermissionManagerService extends IPermissionManager.Stub {
private static final AtomicInteger sAttributionChainIds = new AtomicInteger(0);
private final @NonNull Context mContext;
- private final @NonNull AppOpsManager mAppOpsManager;
private final @NonNull PermissionManagerServiceInternal mPermissionManagerServiceInternal;
PermissionCheckerService(@NonNull Context context) {
mContext = context;
- mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
mPermissionManagerServiceInternal =
LocalServices.getService(PermissionManagerServiceInternal.class);
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 3afba39ad4af..6a5736269e51 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -279,7 +279,6 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
@NonNull
private final int[] mGlobalGids;
- private final HandlerThread mHandlerThread;
private final Handler mHandler;
private final Context mContext;
private final MetricsLogger mMetricsLogger = new MetricsLogger();
@@ -432,10 +431,10 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
}
}
- mHandlerThread = new ServiceThread(TAG,
+ HandlerThread handlerThread = new ServiceThread(TAG,
Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
+ handlerThread.start();
+ mHandler = new Handler(handlerThread.getLooper());
Watchdog.getInstance().addThread(mHandler);
SystemConfig systemConfig = SystemConfig.getInstance();
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index e2269535d931..a172de0bb0ff 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -4413,8 +4413,8 @@ public final class PowerManagerService extends SystemService
private boolean setPowerModeInternal(int mode, boolean enabled) {
// Maybe filter the event.
- if (mBatterySaverStateMachine == null || (mode == Mode.LAUNCH && enabled
- && mBatterySaverStateMachine.getBatterySaverController().isLaunchBoostDisabled())) {
+ if (mode == Mode.LAUNCH && enabled && mBatterySaverStateMachine != null
+ && mBatterySaverStateMachine.getBatterySaverController().isLaunchBoostDisabled()) {
return false;
}
return mNativeWrapper.nativeSetPowerMode(mode, enabled);
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index 375ef6150830..a4dbce6b2631 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -65,6 +65,7 @@ import com.android.internal.widget.LockSettingsInternal;
import com.android.internal.widget.RebootEscrowListener;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.Watchdog;
import com.android.server.pm.ApexManager;
import com.android.server.recoverysystem.hal.BootControlHIDL;
@@ -112,6 +113,9 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo
private static final int SOCKET_CONNECTION_MAX_RETRY = 30;
+ /** How long to pause the watchdog for when rebooting the device. */
+ private static final int REBOOT_WATCHDOG_PAUSE_DURATION_MS = 20_000;
+
static final String REQUEST_LSKF_TIMESTAMP_PREF_SUFFIX = "_request_lskf_timestamp";
static final String REQUEST_LSKF_COUNT_PREF_SUFFIX = "_request_lskf_count";
@@ -899,7 +903,8 @@ public class RecoverySystemService extends IRecoverySystem.Stub implements Reboo
// Clear the metrics prefs after a successful RoR reboot.
mInjector.getMetricsPrefs().deletePrefsFile();
-
+ Watchdog.getInstance().pauseWatchingCurrentThreadFor(
+ REBOOT_WATCHDOG_PAUSE_DURATION_MS, "reboot can be slow");
PowerManager pm = mInjector.getPowerManager();
pm.reboot(reason);
return RESUME_ON_REBOOT_REBOOT_ERROR_UNSPECIFIED;
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index e3eb5b52bc2e..e5a8a6dd2a3a 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -44,6 +44,9 @@ import android.content.res.XmlResourceParser;
import android.graphics.drawable.Drawable;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricSourceType;
+import android.hardware.biometrics.SensorProperties;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -155,6 +158,8 @@ public class TrustManagerService extends SystemService {
private final LockPatternUtils mLockPatternUtils;
private final UserManager mUserManager;
private final ActivityManager mActivityManager;
+ private FingerprintManager mFingerprintManager;
+ private FaceManager mFaceManager;
private VirtualDeviceManagerInternal mVirtualDeviceManager;
private enum TrustState {
@@ -292,6 +297,8 @@ public class TrustManagerService extends SystemService {
mPackageMonitor.register(mContext, mHandler.getLooper(), UserHandle.ALL, true);
mReceiver.register(mContext);
mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
+ mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
+ mFaceManager = mContext.getSystemService(FaceManager.class);
} else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) {
mTrustAgentsCanRun = true;
refreshAgentList(UserHandle.USER_ALL);
@@ -893,7 +900,19 @@ public class TrustManagerService extends SystemService {
private void notifyKeystoreOfDeviceLockState(int userId, boolean isLocked) {
if (isLocked) {
- Authorization.onDeviceLocked(userId, getBiometricSids(userId));
+ if (android.security.Flags.fixUnlockedDeviceRequiredKeysV2()) {
+ // A profile with unified challenge is unlockable not by its own biometrics and
+ // trust agents, but rather by those of the parent user. Therefore, when protecting
+ // the profile's UnlockedDeviceRequired keys, we must use the parent's list of
+ // biometric SIDs and weak unlock methods, not the profile's.
+ int authUserId = mLockPatternUtils.isProfileWithUnifiedChallenge(userId)
+ ? resolveProfileParent(userId) : userId;
+
+ Authorization.onDeviceLocked(userId, getBiometricSids(authUserId),
+ isWeakUnlockMethodEnabled(authUserId));
+ } else {
+ Authorization.onDeviceLocked(userId, getBiometricSids(userId), false);
+ }
} else {
// Notify Keystore that the device is now unlocked for the user. Note that for unlocks
// with LSKF, this is redundant with the call from LockSettingsService which provides
@@ -1440,16 +1459,59 @@ public class TrustManagerService extends SystemService {
if (biometricManager == null) {
return new long[0];
}
- if (android.security.Flags.fixUnlockedDeviceRequiredKeysV2()
- && mLockPatternUtils.isProfileWithUnifiedChallenge(userId)) {
- // Profiles with unified challenge have their own set of biometrics, but the device
- // unlock happens via the parent user. In this case Keystore needs to be given the list
- // of biometric SIDs from the parent user, not the profile.
- userId = resolveProfileParent(userId);
- }
return biometricManager.getAuthenticatorIds(userId);
}
+ // Returns whether the device can become unlocked for the specified user via one of that user's
+ // non-strong biometrics or trust agents. This assumes that the device is currently locked, or
+ // is becoming locked, for the user.
+ private boolean isWeakUnlockMethodEnabled(int userId) {
+
+ // Check whether the system currently allows the use of non-strong biometrics for the user,
+ // *and* the user actually has a non-strong biometric enrolled.
+ //
+ // The biometrics framework ostensibly supports multiple sensors per modality. However,
+ // that feature is unused and untested. So, we simply consider one sensor per modality.
+ //
+ // Also, currently we just consider fingerprint and face, matching Keyguard. If Keyguard
+ // starts supporting other biometric modalities, this will need to be updated.
+ if (mStrongAuthTracker.isBiometricAllowedForUser(/* isStrongBiometric= */ false, userId)) {
+ DevicePolicyManager dpm = mLockPatternUtils.getDevicePolicyManager();
+ int disabledFeatures = dpm.getKeyguardDisabledFeatures(null, userId);
+
+ if (mFingerprintManager != null
+ && (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT) == 0
+ && mFingerprintManager.hasEnrolledTemplates(userId)
+ && isWeakOrConvenienceSensor(
+ mFingerprintManager.getSensorProperties().get(0))) {
+ Slog.i(TAG, "User is unlockable by non-strong fingerprint auth");
+ return true;
+ }
+
+ if (mFaceManager != null
+ && (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_FACE) == 0
+ && mFaceManager.hasEnrolledTemplates(userId)
+ && isWeakOrConvenienceSensor(mFaceManager.getSensorProperties().get(0))) {
+ Slog.i(TAG, "User is unlockable by non-strong face auth");
+ return true;
+ }
+ }
+
+ // Check whether it's possible for the device to be actively unlocked by a trust agent.
+ if (getUserTrustStateInner(userId) == TrustState.TRUSTABLE
+ || (isAutomotive() && isTrustUsuallyManagedInternal(userId))) {
+ Slog.i(TAG, "User is unlockable by trust agent");
+ return true;
+ }
+
+ return false;
+ }
+
+ private static boolean isWeakOrConvenienceSensor(SensorProperties sensor) {
+ return sensor.getSensorStrength() == SensorProperties.STRENGTH_WEAK
+ || sensor.getSensorStrength() == SensorProperties.STRENGTH_CONVENIENCE;
+ }
+
// User lifecycle
@Override
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index d903ad4d9f0d..73fc8e9cfbc4 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -2280,6 +2280,26 @@ public final class TvInputManagerService extends SystemService {
}
@Override
+ public void setVideoFrozen(IBinder sessionToken, boolean isFrozen, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "setVideoFrozen");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ getSessionLocked(sessionToken, callingUid, resolvedUserId)
+ .setVideoFrozen(isFrozen);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slog.e(TAG, "error in setVideoFrozen", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void setTvMessageEnabled(IBinder sessionToken, int type, boolean enabled,
int userId) {
final int callingUid = Binder.getCallingUid();
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index 0467d0cd351d..7ab075e2f3a7 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -38,7 +38,16 @@ import android.media.tv.BroadcastInfoRequest;
import android.media.tv.BroadcastInfoResponse;
import android.media.tv.TvRecordingInfo;
import android.media.tv.TvTrackInfo;
+import android.media.tv.ad.ITvAdClient;
import android.media.tv.ad.ITvAdManager;
+import android.media.tv.ad.ITvAdManagerCallback;
+import android.media.tv.ad.ITvAdService;
+import android.media.tv.ad.ITvAdServiceCallback;
+import android.media.tv.ad.ITvAdSession;
+import android.media.tv.ad.ITvAdSessionCallback;
+import android.media.tv.ad.TvAdService;
+import android.media.tv.ad.TvAdServiceInfo;
+import android.media.tv.flags.Flags;
import android.media.tv.interactive.AppLinkInfo;
import android.media.tv.interactive.ITvInteractiveAppClient;
import android.media.tv.interactive.ITvInteractiveAppManager;
@@ -110,6 +119,8 @@ public class TvInteractiveAppManagerService extends SystemService {
@GuardedBy("mLock")
private boolean mGetServiceListCalled = false;
@GuardedBy("mLock")
+ private boolean mGetAdServiceListCalled = false;
+ @GuardedBy("mLock")
private boolean mGetAppLinkInfoListCalled = false;
private final UserManager mUserManager;
@@ -256,6 +267,141 @@ public class TvInteractiveAppManagerService extends SystemService {
}
@GuardedBy("mLock")
+ private void buildTvAdServiceListLocked(int userId, String[] updatedPackages) {
+ if (!Flags.enableAdServiceFw()) {
+ return;
+ }
+ UserState userState = getOrCreateUserStateLocked(userId);
+ userState.mPackageSet.clear();
+
+ if (DEBUG) {
+ Slogf.d(TAG, "buildTvAdServiceListLocked");
+ }
+ PackageManager pm = mContext.getPackageManager();
+ List<ResolveInfo> services = pm.queryIntentServicesAsUser(
+ new Intent(TvAdService.SERVICE_INTERFACE),
+ PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
+ userId);
+ List<TvAdServiceInfo> serviceList = new ArrayList<>();
+
+ for (ResolveInfo ri : services) {
+ ServiceInfo si = ri.serviceInfo;
+ if (!android.Manifest.permission.BIND_TV_AD_SERVICE.equals(si.permission)) {
+ Slog.w(TAG, "Skipping TV AD service " + si.name
+ + ": it does not require the permission "
+ + android.Manifest.permission.BIND_TV_AD_SERVICE);
+ continue;
+ }
+
+ ComponentName component = new ComponentName(si.packageName, si.name);
+ try {
+ TvAdServiceInfo info = new TvAdServiceInfo(mContext, component);
+ serviceList.add(info);
+ } catch (Exception e) {
+ Slogf.e(TAG, "failed to load TV AD service " + si.name, e);
+ continue;
+ }
+ userState.mPackageSet.add(si.packageName);
+ }
+
+ // sort the service list by service id
+ Collections.sort(serviceList, Comparator.comparing(TvAdServiceInfo::getId));
+ Map<String, TvAdServiceState> adServiceMap = new HashMap<>();
+ for (TvAdServiceInfo info : serviceList) {
+ String serviceId = info.getId();
+ if (DEBUG) {
+ Slogf.d(TAG, "add " + serviceId);
+ }
+ TvAdServiceState adServiceState = userState.mAdServiceMap.get(serviceId);
+ if (adServiceState == null) {
+ adServiceState = new TvAdServiceState();
+ }
+ adServiceState.mInfo = info;
+ adServiceState.mUid = getAdServiceUid(info);
+ adServiceState.mComponentName = info.getComponent();
+ adServiceMap.put(serviceId, adServiceState);
+ }
+
+ for (String serviceId : adServiceMap.keySet()) {
+ if (!userState.mAdServiceMap.containsKey(serviceId)) {
+ notifyAdServiceAddedLocked(userState, serviceId);
+ } else if (updatedPackages != null) {
+ // Notify the package updates
+ ComponentName component = adServiceMap.get(serviceId).mInfo.getComponent();
+ for (String updatedPackage : updatedPackages) {
+ if (component.getPackageName().equals(updatedPackage)) {
+ updateAdServiceConnectionLocked(component, userId);
+ notifyAdServiceUpdatedLocked(userState, serviceId);
+ break;
+ }
+ }
+ }
+ }
+
+ for (String serviceId : userState.mAdServiceMap.keySet()) {
+ if (!adServiceMap.containsKey(serviceId)) {
+ TvAdServiceInfo info = userState.mAdServiceMap.get(serviceId).mInfo;
+ AdServiceState serviceState = userState.mAdServiceStateMap.get(info.getComponent());
+ if (serviceState != null) {
+ abortPendingCreateAdSessionRequestsLocked(serviceState, serviceId, userId);
+ }
+ notifyAdServiceRemovedLocked(userState, serviceId);
+ }
+ }
+
+ userState.mIAppMap.clear();
+ userState.mAdServiceMap = adServiceMap;
+ }
+
+ @GuardedBy("mLock")
+ private void notifyAdServiceAddedLocked(UserState userState, String serviceId) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyAdServiceAddedLocked(serviceId=" + serviceId + ")");
+ }
+ int n = userState.mAdCallbacks.beginBroadcast();
+ for (int i = 0; i < n; ++i) {
+ try {
+ userState.mAdCallbacks.getBroadcastItem(i).onAdServiceAdded(serviceId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "failed to report added AD service to callback", e);
+ }
+ }
+ userState.mAdCallbacks.finishBroadcast();
+ }
+
+ @GuardedBy("mLock")
+ private void notifyAdServiceRemovedLocked(UserState userState, String serviceId) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyAdServiceRemovedLocked(serviceId=" + serviceId + ")");
+ }
+ int n = userState.mAdCallbacks.beginBroadcast();
+ for (int i = 0; i < n; ++i) {
+ try {
+ userState.mAdCallbacks.getBroadcastItem(i).onAdServiceRemoved(serviceId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "failed to report removed AD service to callback", e);
+ }
+ }
+ userState.mAdCallbacks.finishBroadcast();
+ }
+
+ @GuardedBy("mLock")
+ private void notifyAdServiceUpdatedLocked(UserState userState, String serviceId) {
+ if (DEBUG) {
+ Slog.d(TAG, "notifyAdServiceUpdatedLocked(serviceId=" + serviceId + ")");
+ }
+ int n = userState.mAdCallbacks.beginBroadcast();
+ for (int i = 0; i < n; ++i) {
+ try {
+ userState.mAdCallbacks.getBroadcastItem(i).onAdServiceUpdated(serviceId);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "failed to report updated AD service to callback", e);
+ }
+ }
+ userState.mAdCallbacks.finishBroadcast();
+ }
+
+ @GuardedBy("mLock")
private void notifyInteractiveAppServiceAddedLocked(UserState userState, String iAppServiceId) {
if (DEBUG) {
Slog.d(TAG, "notifyInteractiveAppServiceAddedLocked(iAppServiceId="
@@ -340,6 +486,16 @@ public class TvInteractiveAppManagerService extends SystemService {
}
}
+ private int getAdServiceUid(TvAdServiceInfo info) {
+ try {
+ return getContext().getPackageManager().getApplicationInfo(
+ info.getServiceInfo().packageName, 0).uid;
+ } catch (PackageManager.NameNotFoundException e) {
+ Slogf.w(TAG, "Unable to get UID for " + info, e);
+ return Process.INVALID_UID;
+ }
+ }
+
@Override
public void onStart() {
if (DEBUG) {
@@ -357,6 +513,7 @@ public class TvInteractiveAppManagerService extends SystemService {
synchronized (mLock) {
buildTvInteractiveAppServiceListLocked(mCurrentUserId, null);
buildAppLinkInfoLocked(mCurrentUserId);
+ buildTvAdServiceListLocked(mCurrentUserId, null);
}
}
}
@@ -372,6 +529,14 @@ public class TvInteractiveAppManagerService extends SystemService {
}
}
}
+ private void buildTvAdServiceList(String[] packages) {
+ int userId = getChangingUserId();
+ synchronized (mLock) {
+ if (mCurrentUserId == userId || mRunningProfiles.contains(userId)) {
+ buildTvAdServiceListLocked(userId, packages);
+ }
+ }
+ }
@Override
public void onPackageUpdateFinished(String packageName, int uid) {
@@ -379,6 +544,7 @@ public class TvInteractiveAppManagerService extends SystemService {
// This callback is invoked when the TV interactive App service is reinstalled.
// In this case, isReplacing() always returns true.
buildTvInteractiveAppServiceList(new String[] { packageName });
+ buildTvAdServiceList(new String[] { packageName });
}
@Override
@@ -390,6 +556,7 @@ public class TvInteractiveAppManagerService extends SystemService {
// available.
if (isReplacing()) {
buildTvInteractiveAppServiceList(packages);
+ buildTvAdServiceList(packages);
}
}
@@ -403,6 +570,7 @@ public class TvInteractiveAppManagerService extends SystemService {
}
if (isReplacing()) {
buildTvInteractiveAppServiceList(packages);
+ buildTvAdServiceList(packages);
}
}
@@ -418,6 +586,7 @@ public class TvInteractiveAppManagerService extends SystemService {
return;
}
buildTvInteractiveAppServiceList(null);
+ buildTvAdServiceList(null);
}
@Override
@@ -476,6 +645,7 @@ public class TvInteractiveAppManagerService extends SystemService {
mCurrentUserId = userId;
buildTvInteractiveAppServiceListLocked(userId, null);
buildAppLinkInfoLocked(userId);
+ buildTvAdServiceListLocked(userId, null);
}
}
@@ -562,6 +732,7 @@ public class TvInteractiveAppManagerService extends SystemService {
mRunningProfiles.add(userId);
buildTvInteractiveAppServiceListLocked(userId, null);
buildAppLinkInfoLocked(userId);
+ buildTvAdServiceListLocked(userId, null);
}
@GuardedBy("mLock")
@@ -619,7 +790,19 @@ public class TvInteractiveAppManagerService extends SystemService {
Slog.e(TAG, "error in onSessionReleased", e);
}
}
- removeSessionStateLocked(state.mSessionToken, state.mUserId);
+ removeAdSessionStateLocked(state.mSessionToken, state.mUserId);
+ }
+
+ @GuardedBy("mLock")
+ private void clearAdSessionAndNotifyClientLocked(AdSessionState state) {
+ if (state.mClient != null) {
+ try {
+ state.mClient.onSessionReleased(state.mSeq);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in onSessionReleased", e);
+ }
+ }
+ removeAdSessionStateLocked(state.mSessionToken, state.mUserId);
}
private int resolveCallingUserId(int callingPid, int callingUid, int requestedUserId,
@@ -655,6 +838,44 @@ public class TvInteractiveAppManagerService extends SystemService {
}
@GuardedBy("mLock")
+ private AdSessionState getAdSessionStateLocked(
+ IBinder sessionToken, int callingUid, int userId) {
+ UserState userState = getOrCreateUserStateLocked(userId);
+ return getAdSessionStateLocked(sessionToken, callingUid, userState);
+ }
+
+ @GuardedBy("mLock")
+ private AdSessionState getAdSessionStateLocked(IBinder sessionToken, int callingUid,
+ UserState userState) {
+ AdSessionState sessionState = userState.mAdSessionStateMap.get(sessionToken);
+ if (sessionState == null) {
+ throw new SessionNotFoundException("Session state not found for token " + sessionToken);
+ }
+ // Only the application that requested this session or the system can access it.
+ if (callingUid != Process.SYSTEM_UID && callingUid != sessionState.mCallingUid) {
+ throw new SecurityException("Illegal access to the session with token " + sessionToken
+ + " from uid " + callingUid);
+ }
+ return sessionState;
+ }
+
+ @GuardedBy("mLock")
+ private ITvAdSession getAdSessionLocked(
+ IBinder sessionToken, int callingUid, int userId) {
+ return getAdSessionLocked(getAdSessionStateLocked(sessionToken, callingUid, userId));
+ }
+
+ @GuardedBy("mLock")
+ private ITvAdSession getAdSessionLocked(AdSessionState sessionState) {
+ ITvAdSession session = sessionState.mSession;
+ if (session == null) {
+ throw new IllegalStateException("Session not yet created for token "
+ + sessionState.mSessionToken);
+ }
+ return session;
+ }
+
+ @GuardedBy("mLock")
private SessionState getSessionStateLocked(IBinder sessionToken, int callingUid, int userId) {
UserState userState = getOrCreateUserStateLocked(userId);
return getSessionStateLocked(sessionToken, callingUid, userState);
@@ -691,10 +912,200 @@ public class TvInteractiveAppManagerService extends SystemService {
return session;
}
private final class TvAdBinderService extends ITvAdManager.Stub {
+
+ @Override
+ public List<TvAdServiceInfo> getTvAdServiceList(int userId) {
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, "getTvAdServiceList");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ if (!mGetAdServiceListCalled) {
+ buildTvAdServiceListLocked(userId, null);
+ mGetAdServiceListCalled = true;
+ }
+ UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+ List<TvAdServiceInfo> adServiceList = new ArrayList<>();
+ for (TvAdServiceState state : userState.mAdServiceMap.values()) {
+ adServiceList.add(state.mInfo);
+ }
+ return adServiceList;
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void createSession(final ITvAdClient client, final String serviceId, String type,
+ int seq, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
+ userId, "createSession");
+ final long identity = Binder.clearCallingIdentity();
+
+ try {
+ synchronized (mLock) {
+ if (userId != mCurrentUserId && !mRunningProfiles.contains(userId)) {
+ // Only current user and its running profiles can create sessions.
+ // Let the client get onConnectionFailed callback for this case.
+ sendAdSessionTokenToClientLocked(client, serviceId, null, null, seq);
+ return;
+ }
+ UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+ TvAdServiceState adState = userState.mAdMap.get(serviceId);
+ if (adState == null) {
+ Slogf.w(TAG, "Failed to find state for serviceId=" + serviceId);
+ sendAdSessionTokenToClientLocked(client, serviceId, null, null, seq);
+ return;
+ }
+ AdServiceState serviceState =
+ userState.mAdServiceStateMap.get(adState.mComponentName);
+ if (serviceState == null) {
+ int tasUid = PackageManager.getApplicationInfoAsUserCached(
+ adState.mComponentName.getPackageName(), 0, resolvedUserId).uid;
+ serviceState = new AdServiceState(
+ adState.mComponentName, serviceId, resolvedUserId);
+ userState.mAdServiceStateMap.put(adState.mComponentName, serviceState);
+ }
+ // Send a null token immediately while reconnecting.
+ if (serviceState.mReconnecting) {
+ sendAdSessionTokenToClientLocked(client, serviceId, null, null, seq);
+ return;
+ }
+
+ // Create a new session token and a session state.
+ IBinder sessionToken = new Binder();
+ AdSessionState sessionState = new AdSessionState(sessionToken, serviceId, type,
+ adState.mComponentName, client, seq, callingUid,
+ callingPid, resolvedUserId);
+
+ // Add them to the global session state map of the current user.
+ userState.mAdSessionStateMap.put(sessionToken, sessionState);
+
+ // Also, add them to the session state map of the current service.
+ serviceState.mSessionTokens.add(sessionToken);
+
+ if (serviceState.mService != null) {
+ if (!createAdSessionInternalLocked(serviceState.mService, sessionToken,
+ resolvedUserId)) {
+ removeAdSessionStateLocked(sessionToken, resolvedUserId);
+ }
+ } else {
+ updateAdServiceConnectionLocked(adState.mComponentName, resolvedUserId);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void releaseSession(IBinder sessionToken, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "releaseSession(sessionToken=" + sessionToken + ")");
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "releaseSession");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void setSurface(IBinder sessionToken, Surface surface, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "setSurface");
+ AdSessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getAdSessionLocked(sessionState).setSurface(surface);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in setSurface", e);
+ }
+ }
+ } finally {
+ if (surface != null) {
+ // surface is not used in TvInteractiveAppManagerService.
+ surface.release();
+ }
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void dispatchSurfaceChanged(IBinder sessionToken, int format, int width,
+ int height, int userId) {
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "dispatchSurfaceChanged");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ AdSessionState sessionState = getAdSessionStateLocked(
+ sessionToken, callingUid, resolvedUserId);
+ getAdSessionLocked(sessionState).dispatchSurfaceChanged(format, width,
+ height);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in dispatchSurfaceChanged", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
@Override
public void startAdService(IBinder sessionToken, int userId) {
}
+ @Override
+ public void registerCallback(final ITvAdManagerCallback callback, int userId) {
+ int callingPid = Binder.getCallingPid();
+ int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+ "registerCallback");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ final UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+ if (!userState.mAdCallbacks.register(callback)) {
+ Slog.e(TAG, "client process has already died");
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
+ public void unregisterCallback(ITvAdManagerCallback callback, int userId) {
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(),
+ Binder.getCallingUid(), userId, "unregisterCallback");
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ UserState userState = getOrCreateUserStateLocked(resolvedUserId);
+ userState.mAdCallbacks.unregister(callback);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
}
private final class BinderService extends ITvInteractiveAppManager.Stub {
@@ -927,7 +1338,7 @@ public class TvInteractiveAppManagerService extends SystemService {
final long identity = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- releaseSessionLocked(sessionToken, callingUid, resolvedUserId);
+ releaseAdSessionLocked(sessionToken, callingUid, resolvedUserId);
}
} finally {
Binder.restoreCallingIdentity(identity);
@@ -1471,6 +1882,32 @@ public class TvInteractiveAppManagerService extends SystemService {
}
@Override
+ public void sendSelectedTrackInfo(IBinder sessionToken, List<TvTrackInfo> tracks,
+ int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "sendSelectedTrackInfo(tracks=%s)", tracks.toString());
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ userId, "sendSelectedTrackInfo");
+ SessionState sessionState = null;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ try {
+ sessionState = getSessionStateLocked(sessionToken, callingUid,
+ resolvedUserId);
+ getSessionLocked(sessionState).sendSelectedTrackInfo(tracks);
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in sendSelectedTrackInfo", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ @Override
public void sendCurrentTvInputId(IBinder sessionToken, String inputId, int userId) {
if (DEBUG) {
Slogf.d(TAG, "sendCurrentTvInputId(inputId=%s)", inputId);
@@ -2134,6 +2571,17 @@ public class TvInteractiveAppManagerService extends SystemService {
}
@GuardedBy("mLock")
+ private void sendAdSessionTokenToClientLocked(
+ ITvAdClient client, String serviceId, IBinder sessionToken,
+ InputChannel channel, int seq) {
+ try {
+ client.onSessionCreated(serviceId, sessionToken, channel, seq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onSessionCreated", e);
+ }
+ }
+
+ @GuardedBy("mLock")
private boolean createSessionInternalLocked(
ITvInteractiveAppService service, IBinder sessionToken, int userId) {
UserState userState = getOrCreateUserStateLocked(userId);
@@ -2163,6 +2611,58 @@ public class TvInteractiveAppManagerService extends SystemService {
}
@GuardedBy("mLock")
+ private boolean createAdSessionInternalLocked(
+ ITvAdService service, IBinder sessionToken, int userId) {
+ UserState userState = getOrCreateUserStateLocked(userId);
+ AdSessionState sessionState = userState.mAdSessionStateMap.get(sessionToken);
+ if (DEBUG) {
+ Slogf.d(TAG, "createAdSessionInternalLocked(iAppServiceId="
+ + sessionState.mAdServiceId + ")");
+ }
+ InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
+
+ // Set up a callback to send the session token.
+ ITvAdSessionCallback callback = new AdSessionCallback(sessionState, channels);
+
+ boolean created = true;
+ // Create a session. When failed, send a null token immediately.
+ try {
+ service.createSession(
+ channels[1], callback, sessionState.mAdServiceId, sessionState.mType);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in createSession", e);
+ sendAdSessionTokenToClientLocked(sessionState.mClient, sessionState.mAdServiceId, null,
+ null, sessionState.mSeq);
+ created = false;
+ }
+ channels[1].dispose();
+ return created;
+ }
+
+ @GuardedBy("mLock")
+ @Nullable
+ private AdSessionState releaseAdSessionLocked(
+ IBinder sessionToken, int callingUid, int userId) {
+ AdSessionState sessionState = null;
+ try {
+ sessionState = getAdSessionStateLocked(sessionToken, callingUid, userId);
+ UserState userState = getOrCreateUserStateLocked(userId);
+ if (sessionState.mSession != null) {
+ sessionState.mSession.asBinder().unlinkToDeath(sessionState, 0);
+ sessionState.mSession.release();
+ }
+ } catch (RemoteException | SessionNotFoundException e) {
+ Slogf.e(TAG, "error in releaseSession", e);
+ } finally {
+ if (sessionState != null) {
+ sessionState.mSession = null;
+ }
+ }
+ removeAdSessionStateLocked(sessionToken, userId);
+ return sessionState;
+ }
+
+ @GuardedBy("mLock")
@Nullable
private SessionState releaseSessionLocked(IBinder sessionToken, int callingUid, int userId) {
SessionState sessionState = null;
@@ -2215,6 +2715,36 @@ public class TvInteractiveAppManagerService extends SystemService {
}
@GuardedBy("mLock")
+ private void removeAdSessionStateLocked(IBinder sessionToken, int userId) {
+ UserState userState = getOrCreateUserStateLocked(userId);
+
+ // Remove the session state from the global session state map of the current user.
+ AdSessionState sessionState = userState.mAdSessionStateMap.remove(sessionToken);
+
+ if (sessionState == null) {
+ Slogf.e(TAG, "sessionState null, no more remove session action!");
+ return;
+ }
+
+ // Also remove the session token from the session token list of the current client and
+ // service.
+ ClientState clientState = userState.mClientStateMap.get(sessionState.mClient.asBinder());
+ if (clientState != null) {
+ clientState.mSessionTokens.remove(sessionToken);
+ if (clientState.isEmpty()) {
+ userState.mClientStateMap.remove(sessionState.mClient.asBinder());
+ sessionState.mClient.asBinder().unlinkToDeath(clientState, 0);
+ }
+ }
+
+ AdServiceState serviceState = userState.mAdServiceStateMap.get(sessionState.mComponent);
+ if (serviceState != null) {
+ serviceState.mSessionTokens.remove(sessionToken);
+ }
+ updateAdServiceConnectionLocked(sessionState.mComponent, userId);
+ }
+
+ @GuardedBy("mLock")
private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
String iAppServiceId, int userId) {
// Let clients know the create session requests are failed.
@@ -2237,6 +2767,28 @@ public class TvInteractiveAppManagerService extends SystemService {
}
@GuardedBy("mLock")
+ private void abortPendingCreateAdSessionRequestsLocked(AdServiceState serviceState,
+ String serviceId, int userId) {
+ // Let clients know the create session requests are failed.
+ UserState userState = getOrCreateUserStateLocked(userId);
+ List<AdSessionState> sessionsToAbort = new ArrayList<>();
+ for (IBinder sessionToken : serviceState.mSessionTokens) {
+ AdSessionState sessionState = userState.mAdSessionStateMap.get(sessionToken);
+ if (sessionState.mSession == null
+ && (serviceState == null
+ || sessionState.mAdServiceId.equals(serviceId))) {
+ sessionsToAbort.add(sessionState);
+ }
+ }
+ for (AdSessionState sessionState : sessionsToAbort) {
+ removeAdSessionStateLocked(sessionState.mSessionToken, sessionState.mUserId);
+ sendAdSessionTokenToClientLocked(sessionState.mClient,
+ sessionState.mAdServiceId, null, null, sessionState.mSeq);
+ }
+ updateAdServiceConnectionLocked(serviceState.mComponent, userId);
+ }
+
+ @GuardedBy("mLock")
private void updateServiceConnectionLocked(ComponentName component, int userId) {
UserState userState = getOrCreateUserStateLocked(userId);
ServiceState serviceState = userState.mServiceStateMap.get(component);
@@ -2284,10 +2836,64 @@ public class TvInteractiveAppManagerService extends SystemService {
}
}
+ @GuardedBy("mLock")
+ private void updateAdServiceConnectionLocked(ComponentName component, int userId) {
+ UserState userState = getOrCreateUserStateLocked(userId);
+ AdServiceState serviceState = userState.mAdServiceStateMap.get(component);
+ if (serviceState == null) {
+ return;
+ }
+ if (serviceState.mReconnecting) {
+ if (!serviceState.mSessionTokens.isEmpty()) {
+ // wait until all the sessions are removed.
+ return;
+ }
+ serviceState.mReconnecting = false;
+ }
+
+ boolean shouldBind = (!serviceState.mSessionTokens.isEmpty())
+ || (!serviceState.mPendingAppLinkCommand.isEmpty());
+
+ if (serviceState.mService == null && shouldBind) {
+ // This means that the service is not yet connected but its state indicates that we
+ // have pending requests. Then, connect the service.
+ if (serviceState.mBound) {
+ // We have already bound to the service so we don't try to bind again until after we
+ // unbind later on.
+ return;
+ }
+ if (DEBUG) {
+ Slogf.d(TAG, "bindServiceAsUser(service=" + component + ", userId=" + userId + ")");
+ }
+
+ Intent i = new Intent(TvAdService.SERVICE_INTERFACE).setComponent(component);
+ serviceState.mBound = mContext.bindServiceAsUser(
+ i, serviceState.mConnection,
+ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
+ new UserHandle(userId));
+ } else if (serviceState.mService != null && !shouldBind) {
+ // This means that the service is already connected but its state indicates that we have
+ // nothing to do with it. Then, disconnect the service.
+ if (DEBUG) {
+ Slogf.d(TAG, "unbindService(service=" + component + ")");
+ }
+ mContext.unbindService(serviceState.mConnection);
+ userState.mAdServiceStateMap.remove(component);
+ }
+ }
+
private static final class UserState {
private final int mUserId;
+ // A mapping from the TV AD service ID to its TvAdServiceState.
+ private Map<String, TvAdServiceState> mAdMap = new HashMap<>();
+ // A mapping from the name of a TV Interactive App service to its state.
+ private final Map<ComponentName, AdServiceState> mAdServiceStateMap = new HashMap<>();
+ // A mapping from the token of a TV Interactive App session to its state.
+ private final Map<IBinder, AdSessionState> mAdSessionStateMap = new HashMap<>();
// A mapping from the TV Interactive App ID to its TvInteractiveAppState.
private Map<String, TvInteractiveAppState> mIAppMap = new HashMap<>();
+ // A mapping from the TV AD service ID to its TvAdServiceState.
+ private Map<String, TvAdServiceState> mAdServiceMap = new HashMap<>();
// A mapping from the token of a client to its state.
private final Map<IBinder, ClientState> mClientStateMap = new HashMap<>();
// A mapping from the name of a TV Interactive App service to its state.
@@ -2299,6 +2905,8 @@ public class TvInteractiveAppManagerService extends SystemService {
private final Set<String> mPackageSet = new HashSet<>();
// A list of all app link infos.
private final List<AppLinkInfo> mAppLinkInfoList = new ArrayList<>();
+ private final RemoteCallbackList<ITvAdManagerCallback> mAdCallbacks =
+ new RemoteCallbackList<>();
// A list of callbacks.
private final RemoteCallbackList<ITvInteractiveAppManagerCallback> mCallbacks =
@@ -2317,7 +2925,16 @@ public class TvInteractiveAppManagerService extends SystemService {
private int mIAppNumber;
}
+ private static final class TvAdServiceState {
+ private String mAdServiceId;
+ private ComponentName mComponentName;
+ private TvAdServiceInfo mInfo;
+ private int mUid;
+ private int mAdNumber;
+ }
+
private final class SessionState implements IBinder.DeathRecipient {
+ // TODO: rename SessionState and reorganize classes / methods of this file
private final IBinder mSessionToken;
private ITvInteractiveAppSession mSession;
private final String mIAppServiceId;
@@ -2359,6 +2976,49 @@ public class TvInteractiveAppManagerService extends SystemService {
}
}
+ private final class AdSessionState implements IBinder.DeathRecipient {
+ private final IBinder mSessionToken;
+ private ITvAdSession mSession;
+ private final String mAdServiceId;
+
+ private final String mType;
+ private final ITvAdClient mClient;
+ private final int mSeq;
+ private final ComponentName mComponent;
+
+ // The UID of the application that created the session.
+ // The application is usually the TV app.
+ private final int mCallingUid;
+
+ // The PID of the application that created the session.
+ // The application is usually the TV app.
+ private final int mCallingPid;
+
+ private final int mUserId;
+
+ private AdSessionState(IBinder sessionToken, String serviceId, String type,
+ ComponentName componentName, ITvAdClient client, int seq,
+ int callingUid, int callingPid, int userId) {
+ mSessionToken = sessionToken;
+ mAdServiceId = serviceId;
+ mType = type;
+ mComponent = componentName;
+ mClient = client;
+ mSeq = seq;
+ mCallingUid = callingUid;
+ mCallingPid = callingPid;
+ mUserId = userId;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ mSession = null;
+ clearAdSessionAndNotifyClientLocked(this);
+ }
+ }
+ }
+
private final class ClientState implements IBinder.DeathRecipient {
private final List<IBinder> mSessionTokens = new ArrayList<>();
@@ -2429,6 +3089,29 @@ public class TvInteractiveAppManagerService extends SystemService {
}
}
+ private final class AdServiceState {
+ private final List<IBinder> mSessionTokens = new ArrayList<>();
+ private final ServiceConnection mConnection;
+ private final ComponentName mComponent;
+ private final String mAdServiceId;
+ private final List<Bundle> mPendingAppLinkCommand = new ArrayList<>();
+
+ private ITvAdService mService;
+ private AdServiceCallback mCallback;
+ private boolean mBound;
+ private boolean mReconnecting;
+
+ private AdServiceState(ComponentName component, String tasId, int userId) {
+ mComponent = component;
+ mConnection = new AdServiceConnection(component, userId);
+ mAdServiceId = tasId;
+ }
+
+ private void addPendingAppLinkCommand(Bundle command) {
+ mPendingAppLinkCommand.add(command);
+ }
+ }
+
private final class InteractiveAppServiceConnection implements ServiceConnection {
private final ComponentName mComponent;
private final int mUserId;
@@ -2542,6 +3225,98 @@ public class TvInteractiveAppManagerService extends SystemService {
}
}
+ private final class AdServiceConnection implements ServiceConnection {
+ private final ComponentName mComponent;
+ private final int mUserId;
+
+ private AdServiceConnection(ComponentName component, int userId) {
+ mComponent = component;
+ mUserId = userId;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName component, IBinder service) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onServiceConnected(component=" + component + ")");
+ }
+ synchronized (mLock) {
+ UserState userState = getUserStateLocked(mUserId);
+ if (userState == null) {
+ // The user was removed while connecting.
+ mContext.unbindService(this);
+ return;
+ }
+ AdServiceState serviceState = userState.mAdServiceStateMap.get(mComponent);
+ serviceState.mService = ITvAdService.Stub.asInterface(service);
+
+ // Register a callback, if we need to.
+ if (serviceState.mCallback == null) {
+ serviceState.mCallback = new AdServiceCallback(mComponent, mUserId);
+ try {
+ serviceState.mService.registerCallback(serviceState.mCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "error in registerCallback", e);
+ }
+ }
+
+ if (!serviceState.mPendingAppLinkCommand.isEmpty()) {
+ for (Iterator<Bundle> it = serviceState.mPendingAppLinkCommand.iterator();
+ it.hasNext(); ) {
+ Bundle command = it.next();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ serviceState.mService.sendAppLinkCommand(command);
+ it.remove();
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in sendAppLinkCommand(" + command
+ + ") when onServiceConnected", e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ List<IBinder> tokensToBeRemoved = new ArrayList<>();
+
+ // And create sessions, if any.
+ for (IBinder sessionToken : serviceState.mSessionTokens) {
+ if (!createAdSessionInternalLocked(
+ serviceState.mService, sessionToken, mUserId)) {
+ tokensToBeRemoved.add(sessionToken);
+ }
+ }
+
+ for (IBinder sessionToken : tokensToBeRemoved) {
+ removeAdSessionStateLocked(sessionToken, mUserId);
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName component) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onServiceDisconnected(component=" + component + ")");
+ }
+ if (!mComponent.equals(component)) {
+ throw new IllegalArgumentException("Mismatched ComponentName: "
+ + mComponent + " (expected), " + component + " (actual).");
+ }
+ synchronized (mLock) {
+ UserState userState = getOrCreateUserStateLocked(mUserId);
+ AdServiceState serviceState = userState.mAdServiceStateMap.get(mComponent);
+ if (serviceState != null) {
+ serviceState.mReconnecting = true;
+ serviceState.mBound = false;
+ serviceState.mService = null;
+ serviceState.mCallback = null;
+
+ abortPendingCreateAdSessionRequestsLocked(serviceState, null, mUserId);
+ }
+ }
+ }
+ }
+
+
private final class ServiceCallback extends ITvInteractiveAppServiceCallback.Stub {
private final ComponentName mComponent;
private final int mUserId;
@@ -2567,6 +3342,17 @@ public class TvInteractiveAppManagerService extends SystemService {
}
}
+
+ private final class AdServiceCallback extends ITvAdServiceCallback.Stub {
+ private final ComponentName mComponent;
+ private final int mUserId;
+
+ AdServiceCallback(ComponentName component, int userId) {
+ mComponent = component;
+ mUserId = userId;
+ }
+ }
+
private final class SessionCallback extends ITvInteractiveAppSessionCallback.Stub {
private final SessionState mSessionState;
private final InputChannel[] mInputChannels;
@@ -2798,6 +3584,23 @@ public class TvInteractiveAppManagerService extends SystemService {
}
@Override
+ public void onRequestSelectedTrackInfo() {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onRequestSelectedTrackInfo");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onRequestSelectedTrackInfo(mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onRequestSelectedTrackInfo", e);
+ }
+ }
+ }
+
+ @Override
public void onRequestCurrentTvInputId() {
synchronized (mLock) {
if (DEBUG) {
@@ -3110,6 +3913,85 @@ public class TvInteractiveAppManagerService extends SystemService {
}
}
+ private final class AdSessionCallback extends ITvAdSessionCallback.Stub {
+ private final AdSessionState mSessionState;
+ private final InputChannel[] mInputChannels;
+
+ AdSessionCallback(AdSessionState sessionState, InputChannel[] channels) {
+ mSessionState = sessionState;
+ mInputChannels = channels;
+ }
+
+ @Override
+ public void onSessionCreated(ITvAdSession session) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onSessionCreated(adServiceId="
+ + mSessionState.mAdServiceId + ")");
+ }
+ synchronized (mLock) {
+ mSessionState.mSession = session;
+ if (session != null && addAdSessionTokenToClientStateLocked(session)) {
+ sendAdSessionTokenToClientLocked(
+ mSessionState.mClient,
+ mSessionState.mAdServiceId,
+ mSessionState.mSessionToken,
+ mInputChannels[0],
+ mSessionState.mSeq);
+ } else {
+ removeAdSessionStateLocked(mSessionState.mSessionToken, mSessionState.mUserId);
+ sendAdSessionTokenToClientLocked(mSessionState.mClient,
+ mSessionState.mAdServiceId, null, null, mSessionState.mSeq);
+ }
+ mInputChannels[0].dispose();
+ }
+ }
+
+ @Override
+ public void onLayoutSurface(int left, int top, int right, int bottom) {
+ synchronized (mLock) {
+ if (DEBUG) {
+ Slogf.d(TAG, "onLayoutSurface (left=" + left + ", top=" + top
+ + ", right=" + right + ", bottom=" + bottom + ",)");
+ }
+ if (mSessionState.mSession == null || mSessionState.mClient == null) {
+ return;
+ }
+ try {
+ mSessionState.mClient.onLayoutSurface(left, top, right, bottom,
+ mSessionState.mSeq);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "error in onLayoutSurface", e);
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean addAdSessionTokenToClientStateLocked(ITvAdSession session) {
+ try {
+ session.asBinder().linkToDeath(mSessionState, 0);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "session process has already died", e);
+ return false;
+ }
+
+ IBinder clientToken = mSessionState.mClient.asBinder();
+ UserState userState = getOrCreateUserStateLocked(mSessionState.mUserId);
+ ClientState clientState = userState.mClientStateMap.get(clientToken);
+ if (clientState == null) {
+ clientState = new ClientState(clientToken, mSessionState.mUserId);
+ try {
+ clientToken.linkToDeath(clientState, 0);
+ } catch (RemoteException e) {
+ Slogf.e(TAG, "client process has already died", e);
+ return false;
+ }
+ userState.mClientStateMap.put(clientToken, clientState);
+ }
+ clientState.mSessionTokens.add(mSessionState.mSessionToken);
+ return true;
+ }
+ }
+
private static class SessionNotFoundException extends IllegalArgumentException {
SessionNotFoundException(String name) {
super(name);
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index e3aba0f6bc6f..c15ac37565ff 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -111,7 +111,7 @@ public class AnrTimer<V> implements AutoCloseable {
* but it can be changed for local testing.
*/
private static boolean anrTimerServiceEnabled() {
- return Flags.anrTimerServiceEnabled();
+ return Flags.anrTimerService();
}
/**
diff --git a/services/core/java/com/android/server/utils/flags.aconfig b/services/core/java/com/android/server/utils/flags.aconfig
index 489e21ab06ca..163116b9c5c7 100644
--- a/services/core/java/com/android/server/utils/flags.aconfig
+++ b/services/core/java/com/android/server/utils/flags.aconfig
@@ -1,7 +1,7 @@
package: "com.android.server.utils"
flag {
- name: "anr_timer_service_enabled"
+ name: "anr_timer_service"
namespace: "system_performance"
is_fixed_read_only: true
description: "Feature flag for the ANR timer service"
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 3782b429f93a..26584315f15c 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1256,7 +1256,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
// migrateStaticSystemToLockWallpaperLocked() and added to the lock wp map
// if successful.
WallpaperData lockWp = mLockWallpaperMap.get(mNewWallpaper.userId);
- if (lockWp != null) {
+ if (lockWp != null && mOriginalSystem.connection != null) {
// Successful rename, set old system+lock to the pending lock wp
if (DEBUG) {
Slog.v(TAG, "static system+lock to system success");
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index 60dc4ff224bc..d95b431752ec 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -348,6 +348,10 @@ class WebViewUpdateServiceImpl implements WebViewUpdateServiceInterface {
private void pinWebviewIfRequired(ApplicationInfo appInfo) {
PinnerService pinnerService = LocalServices.getService(PinnerService.class);
+ if (pinnerService == null) {
+ // This happens in unit tests which do not have services.
+ return;
+ }
int webviewPinQuota = pinnerService.getWebviewPinQuota();
if (webviewPinQuota <= 0) {
return;
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 676203bc746a..2e0546eee8e7 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -778,17 +778,22 @@ class ActivityClientController extends IActivityClientController.Stub {
try {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
+ if (r == null) {
+ return false;
+ }
// Create a transition if the activity is playing in case the below activity didn't
// commit invisible. That's because if any activity below this one has changed its
// visibility while playing transition, there won't able to commit visibility until
// the running transition finish.
- final Transition transition = r != null
- && r.mTransitionController.inPlayingTransition(r)
+ final Transition transition = r.mTransitionController.isShellTransitionsEnabled()
&& !r.mTransitionController.isCollecting()
? r.mTransitionController.createTransition(TRANSIT_TO_BACK) : null;
- final boolean changed = r != null && r.setOccludesParent(true);
+ final boolean changed = r.setOccludesParent(true);
if (transition != null) {
if (changed) {
+ // Always set as scene transition because it expects to be a jump-cut.
+ transition.setOverrideAnimation(TransitionInfo.AnimationOptions
+ .makeSceneTransitionAnimOptions(), null, null);
r.mTransitionController.requestStartTransition(transition,
null /*startTask */, null /* remoteTransition */,
null /* displayChange */);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 69fbe6ba3c29..9b1f9c8441ad 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3093,7 +3093,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final boolean changed = occludesParent != mOccludesParent;
mOccludesParent = occludesParent;
setMainWindowOpaque(occludesParent);
- mWmService.mWindowPlacerLocked.requestTraversal();
if (changed && task != null && !occludesParent) {
getRootTask().convertActivityToTranslucent(this);
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index e7621ffe8e3c..182e1c133790 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -18,6 +18,7 @@ package com.android.server.wm;
import static android.app.ActivityManager.INTENT_SENDER_ACTIVITY;
import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
import static android.app.PendingIntent.FLAG_CANCEL_CURRENT;
import static android.app.PendingIntent.FLAG_IMMUTABLE;
import static android.app.PendingIntent.FLAG_ONE_SHOT;
@@ -144,22 +145,20 @@ class ActivityStartInterceptor {
}
private IntentSender createIntentSenderForOriginalIntent(int callingUid, int flags) {
- Bundle bOptions = deferCrossProfileAppsAnimationIfNecessary();
+ ActivityOptions activityOptions = deferCrossProfileAppsAnimationIfNecessary();
+ activityOptions.setPendingIntentCreatorBackgroundActivityStartMode(
+ MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
final TaskFragment taskFragment = getLaunchTaskFragment();
// If the original intent is going to be embedded, try to forward the embedding TaskFragment
// and its task id to embed back the original intent.
if (taskFragment != null) {
- ActivityOptions activityOptions = bOptions != null
- ? ActivityOptions.fromBundle(bOptions)
- : ActivityOptions.makeBasic();
activityOptions.setLaunchTaskFragmentToken(taskFragment.getFragmentToken());
- bOptions = activityOptions.toBundle();
}
final IIntentSender target = mService.getIntentSenderLocked(
INTENT_SENDER_ACTIVITY, mCallingPackage, mCallingFeatureId, callingUid, mUserId,
null /*token*/, null /*resultCode*/, 0 /*requestCode*/,
new Intent[] { mIntent }, new String[] { mResolvedType },
- flags, bOptions);
+ flags, activityOptions.toBundle());
return new IntentSender(target);
}
@@ -272,12 +271,12 @@ class ActivityStartInterceptor {
*
* @return the activity option used to start the original intent.
*/
- private Bundle deferCrossProfileAppsAnimationIfNecessary() {
+ private ActivityOptions deferCrossProfileAppsAnimationIfNecessary() {
if (hasCrossProfileAnimation()) {
mActivityOptions = null;
- return ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle();
+ return ActivityOptions.makeOpenCrossProfileAppsAnimation();
}
- return null;
+ return ActivityOptions.makeBasic();
}
private boolean interceptQuietProfileIfNeeded() {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 13f71521c240..f6d77ea33598 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -60,6 +60,7 @@ import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TAS
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
+import static com.android.server.pm.PackageArchiver.isArchivingEnabled;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS;
@@ -104,7 +105,6 @@ import android.content.IntentSender;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.AuxiliaryResolveInfo;
-import android.content.pm.Flags;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
@@ -1034,7 +1034,7 @@ class ActivityStarter {
}
if (err == ActivityManager.START_SUCCESS && aInfo == null) {
- if (Flags.archiving()) {
+ if (isArchivingEnabled()) {
PackageArchiver packageArchiver = mService
.getPackageManagerInternalLocked()
.getPackageArchiver();
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 9c9cf04de3cc..0f36d8eafbe4 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -18,6 +18,10 @@ package com.android.server.wm;
import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+import static android.app.ComponentOptions.BackgroundActivityStartMode;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
@@ -31,6 +35,7 @@ import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
+import static com.android.window.flags.Flags.balRequireOptInSameUid;
import static com.android.window.flags.Flags.balShowToasts;
import static com.android.window.flags.Flags.balShowToastsBlocked;
import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
@@ -94,9 +99,9 @@ public class BackgroundActivityStartController {
public static final ActivityOptions ACTIVITY_OPTIONS_SYSTEM_DEFINED =
ActivityOptions.makeBasic()
.setPendingIntentBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED)
+ MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED)
.setPendingIntentCreatorBackgroundActivityStartMode(
- ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
+ MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
private final ActivityTaskManagerService mService;
@@ -240,6 +245,7 @@ public class BackgroundActivityStartController {
private final WindowProcessController mRealCallerApp;
private final boolean mIsCallForResult;
private final ActivityOptions mCheckedOptions;
+ private final String mAutoOptInReason;
private BalVerdict mResultForCaller;
private BalVerdict mResultForRealCaller;
@@ -263,28 +269,38 @@ public class BackgroundActivityStartController {
mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
mIsCallForResult = resultRecord != null;
mCheckedOptions = checkedOptions;
- if (balRequireOptInByPendingIntentCreator() // auto-opt in introduced with this feature
- && (originatingPendingIntent == null // not a PendingIntent
- || mIsCallForResult) // sent for result
- ) {
+ @BackgroundActivityStartMode int callerBackgroundActivityStartMode =
+ checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode();
+ @BackgroundActivityStartMode int realCallerBackgroundActivityStartMode =
+ checkedOptions.getPendingIntentBackgroundActivityStartMode();
+
+ if (balRequireOptInByPendingIntentCreator() && originatingPendingIntent == null) {
+ mAutoOptInReason = "notPendingIntent";
+ } else if (balRequireOptInByPendingIntentCreator() && mIsCallForResult) {
+ mAutoOptInReason = "callForResult";
+ } else if (callingUid == realCallingUid && !balRequireOptInSameUid()) {
+ mAutoOptInReason = "sameUid";
+ } else {
+ mAutoOptInReason = null;
+ }
+
+ if (mAutoOptInReason != null) {
// grant BAL privileges unless explicitly opted out
mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator =
- checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
- == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
- ? BackgroundStartPrivileges.NONE
- : BackgroundStartPrivileges.ALLOW_BAL;
- mBalAllowedByPiSender =
- checkedOptions.getPendingIntentBackgroundActivityStartMode()
- == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
+ callerBackgroundActivityStartMode == MODE_BACKGROUND_ACTIVITY_START_DENIED
? BackgroundStartPrivileges.NONE
: BackgroundStartPrivileges.ALLOW_BAL;
+ mBalAllowedByPiSender = realCallerBackgroundActivityStartMode
+ == MODE_BACKGROUND_ACTIVITY_START_DENIED
+ ? BackgroundStartPrivileges.NONE
+ : BackgroundStartPrivileges.ALLOW_BAL;
} else {
// for PendingIntents we restrict BAL based on target_sdk
mBalAllowedByPiCreatorWithHardening = getBackgroundStartPrivilegesAllowedByCreator(
callingUid, callingPackage, checkedOptions);
final BackgroundStartPrivileges mBalAllowedByPiCreatorWithoutHardening =
- checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
- == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
+ callerBackgroundActivityStartMode
+ == MODE_BACKGROUND_ACTIVITY_START_DENIED
? BackgroundStartPrivileges.NONE
: BackgroundStartPrivileges.ALLOW_BAL;
mBalAllowedByPiCreator = balRequireOptInByPendingIntentCreator()
@@ -326,11 +342,11 @@ public class BackgroundActivityStartController {
private BackgroundStartPrivileges getBackgroundStartPrivilegesAllowedByCreator(
int callingUid, String callingPackage, ActivityOptions checkedOptions) {
switch (checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()) {
- case ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
+ case MODE_BACKGROUND_ACTIVITY_START_ALLOWED:
return BackgroundStartPrivileges.ALLOW_BAL;
- case ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED:
+ case MODE_BACKGROUND_ACTIVITY_START_DENIED:
return BackgroundStartPrivileges.NONE;
- case ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
+ case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
// no explicit choice by the app - let us decide what to do
if (callingPackage != null) {
// determine based on the calling/creating package
@@ -431,6 +447,7 @@ public class BackgroundActivityStartController {
sb.append("; hasRealCaller: ").append(hasRealCaller());
sb.append("; isCallForResult: ").append(mIsCallForResult);
sb.append("; isPendingIntent: ").append(isPendingIntent());
+ sb.append("; autoOptInReason: ").append(mAutoOptInReason);
if (hasRealCaller()) {
sb.append("; realCallingPackage: ")
.append(getDebugPackageName(mRealCallingPackage, mRealCallingUid));
@@ -456,6 +473,50 @@ public class BackgroundActivityStartController {
sb.append("]");
return sb.toString();
}
+
+ public boolean isPendingIntentBalAllowedByPermission() {
+ return PendingIntentRecord.isPendingIntentBalAllowedByPermission(mCheckedOptions);
+ }
+
+ public boolean callerExplicitOptInOrAutoOptIn() {
+ if (mAutoOptInReason == null) {
+ return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+ == MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+ } else {
+ return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+ != MODE_BACKGROUND_ACTIVITY_START_DENIED;
+ }
+ }
+
+ public boolean realCallerExplicitOptInOrAutoOptIn() {
+ if (mAutoOptInReason == null) {
+ return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+ == MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
+ } else {
+ return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+ != MODE_BACKGROUND_ACTIVITY_START_DENIED;
+ }
+ }
+
+ public boolean callerExplicitOptOut() {
+ return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+ == MODE_BACKGROUND_ACTIVITY_START_DENIED;
+ }
+
+ public boolean realCallerExplicitOptOut() {
+ return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+ == MODE_BACKGROUND_ACTIVITY_START_DENIED;
+ }
+
+ public boolean callerExplicitOptInOrOut() {
+ return mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+ != MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+ }
+
+ public boolean realCallerExplicitOptInOrOut() {
+ return mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+ != MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+ }
}
static class BalVerdict {
@@ -624,7 +685,7 @@ public class BackgroundActivityStartController {
// PendingIntents is null).
BalVerdict resultForRealCaller = state.callerIsRealCaller() && resultForCaller.allows()
? resultForCaller
- : checkBackgroundActivityStartAllowedBySender(state, checkedOptions)
+ : checkBackgroundActivityStartAllowedBySender(state)
.setBasedOnRealCaller();
state.setResultForRealCaller(resultForRealCaller);
@@ -634,18 +695,14 @@ public class BackgroundActivityStartController {
}
// Handle cases with explicit opt-in
- if (resultForCaller.allows()
- && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
- == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) {
+ if (resultForCaller.allows() && state.callerExplicitOptInOrAutoOptIn()) {
if (DEBUG_ACTIVITY_STARTS) {
Slog.d(TAG, "Activity start explicitly allowed by caller. "
+ state.dump());
}
return allowBasedOnCaller(state);
}
- if (resultForRealCaller.allows()
- && checkedOptions.getPendingIntentBackgroundActivityStartMode()
- == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) {
+ if (resultForRealCaller.allows() && state.realCallerExplicitOptInOrAutoOptIn()) {
if (DEBUG_ACTIVITY_STARTS) {
Slog.d(TAG, "Activity start explicitly allowed by real caller. "
+ state.dump());
@@ -653,12 +710,9 @@ public class BackgroundActivityStartController {
return allowBasedOnRealCaller(state);
}
// Handle PendingIntent cases with default behavior next
- boolean callerCanAllow = resultForCaller.allows()
- && checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
- == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+ boolean callerCanAllow = resultForCaller.allows() && !state.callerExplicitOptOut();
boolean realCallerCanAllow = resultForRealCaller.allows()
- && checkedOptions.getPendingIntentBackgroundActivityStartMode()
- == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+ && !state.realCallerExplicitOptOut();
if (callerCanAllow && realCallerCanAllow) {
// Both caller and real caller allow with system defined behavior
if (state.mBalAllowedByPiCreatorWithHardening.allowsBackgroundActivityStarts()) {
@@ -880,11 +934,9 @@ public class BackgroundActivityStartController {
* @return A code denoting which BAL rule allows an activity to be started,
* or {@link #BAL_BLOCK} if the launch should be blocked
*/
- BalVerdict checkBackgroundActivityStartAllowedBySender(
- BalState state,
- ActivityOptions checkedOptions) {
+ BalVerdict checkBackgroundActivityStartAllowedBySender(BalState state) {
- if (PendingIntentRecord.isPendingIntentBalAllowedByPermission(checkedOptions)
+ if (state.isPendingIntentBalAllowedByPermission()
&& ActivityManager.checkComponentPermission(
android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND,
state.mRealCallingUid, NO_PROCESS_UID, true) == PackageManager.PERMISSION_GRANTED) {
@@ -1545,13 +1597,11 @@ public class BackgroundActivityStartController {
state.mRealCallingUid,
state.mResultForCaller == null ? BAL_BLOCK : state.mResultForCaller.getRawCode(),
state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts(),
- state.mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
- != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED,
+ state.callerExplicitOptInOrOut(),
state.mResultForRealCaller == null ? BAL_BLOCK
: state.mResultForRealCaller.getRawCode(),
state.mBalAllowedByPiSender.allowsBackgroundActivityStarts(),
- state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
- != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED
+ state.realCallerExplicitOptInOrOut()
);
}
diff --git a/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
new file mode 100644
index 000000000000..5f488b769885
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ScreenRecordingCallbackController.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.content.Context.MEDIA_PROJECTION_SERVICE;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_ERROR;
+
+import android.media.projection.IMediaProjectionManager;
+import android.media.projection.IMediaProjectionWatcherCallback;
+import android.media.projection.MediaProjectionInfo;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.view.ContentRecordingSession;
+import android.window.IScreenRecordingCallback;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.Set;
+
+public class ScreenRecordingCallbackController {
+
+ private final class Callback implements IBinder.DeathRecipient {
+
+ IScreenRecordingCallback mCallback;
+ int mUid;
+
+ Callback(IScreenRecordingCallback callback, int uid) {
+ this.mCallback = callback;
+ this.mUid = uid;
+ }
+
+ public void binderDied() {
+ unregister(mCallback);
+ }
+ }
+
+ @GuardedBy("WindowManagerService.mGlobalLock")
+ private final Map<IBinder, Callback> mCallbacks = new ArrayMap<>();
+
+ @GuardedBy("WindowManagerService.mGlobalLock")
+ private final Map<Integer /*UID*/, Boolean> mLastInvokedStateByUid = new ArrayMap<>();
+
+ private final WindowManagerService mWms;
+
+ @GuardedBy("WindowManagerService.mGlobalLock")
+ private WindowContainer<WindowContainer> mRecordedWC;
+
+ private boolean mWatcherCallbackRegistered = false;
+
+ private final class MediaProjectionWatcherCallback extends
+ IMediaProjectionWatcherCallback.Stub {
+ @Override
+ public void onStart(MediaProjectionInfo mediaProjectionInfo) {
+ onScreenRecordingStart(mediaProjectionInfo);
+ }
+
+ @Override
+ public void onStop(MediaProjectionInfo mediaProjectionInfo) {
+ onScreenRecordingStop();
+ }
+
+ @Override
+ public void onRecordingSessionSet(MediaProjectionInfo mediaProjectionInfo,
+ ContentRecordingSession contentRecordingSession) {
+ }
+ }
+
+ ScreenRecordingCallbackController(WindowManagerService wms) {
+ mWms = wms;
+ }
+
+ @GuardedBy("WindowManagerService.mGlobalLock")
+ private void setRecordedWindowContainer(MediaProjectionInfo mediaProjectionInfo) {
+ if (mediaProjectionInfo.getLaunchCookie() == null) {
+ mRecordedWC = (WindowContainer) mWms.mRoot.getDefaultDisplay();
+ } else {
+ mRecordedWC = mWms.mRoot.getActivity(activity -> activity.mLaunchCookie
+ == mediaProjectionInfo.getLaunchCookie()).getTask();
+ }
+ }
+
+ @GuardedBy("WindowManagerService.mGlobalLock")
+ private void ensureMediaProjectionWatcherCallbackRegistered() {
+ if (mWatcherCallbackRegistered) {
+ return;
+ }
+
+ IBinder binder = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
+ IMediaProjectionManager mediaProjectionManager =
+ IMediaProjectionManager.Stub.asInterface(binder);
+
+ long identityToken = Binder.clearCallingIdentity();
+ MediaProjectionInfo mediaProjectionInfo = null;
+ try {
+ mediaProjectionInfo = mediaProjectionManager.addCallback(
+ new MediaProjectionWatcherCallback());
+ mWatcherCallbackRegistered = true;
+ } catch (RemoteException e) {
+ ProtoLog.e(WM_ERROR, "Failed to register MediaProjectionWatcherCallback");
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+
+ if (mediaProjectionInfo != null) {
+ setRecordedWindowContainer(mediaProjectionInfo);
+ }
+ }
+
+ boolean register(IScreenRecordingCallback callback) {
+ synchronized (mWms.mGlobalLock) {
+ ensureMediaProjectionWatcherCallbackRegistered();
+
+ IBinder binder = callback.asBinder();
+ int uid = Binder.getCallingUid();
+
+ if (mCallbacks.containsKey(binder)) {
+ return mLastInvokedStateByUid.get(uid);
+ }
+
+ Callback callbackInfo = new Callback(callback, uid);
+ try {
+ binder.linkToDeath(callbackInfo, 0);
+ } catch (RemoteException e) {
+ return false;
+ }
+
+ boolean uidInRecording = uidHasRecordedActivity(callbackInfo.mUid);
+ mLastInvokedStateByUid.put(callbackInfo.mUid, uidInRecording);
+ mCallbacks.put(binder, callbackInfo);
+ return uidInRecording;
+ }
+ }
+
+ void unregister(IScreenRecordingCallback callback) {
+ synchronized (mWms.mGlobalLock) {
+ IBinder binder = callback.asBinder();
+ Callback callbackInfo = mCallbacks.remove(binder);
+ binder.unlinkToDeath(callbackInfo, 0);
+
+ boolean uidHasCallback = false;
+ for (Callback cb : mCallbacks.values()) {
+ if (cb.mUid == callbackInfo.mUid) {
+ uidHasCallback = true;
+ break;
+ }
+ }
+ if (!uidHasCallback) {
+ mLastInvokedStateByUid.remove(callbackInfo.mUid);
+ }
+ }
+ }
+
+ private void onScreenRecordingStart(MediaProjectionInfo mediaProjectionInfo) {
+ synchronized (mWms.mGlobalLock) {
+ setRecordedWindowContainer(mediaProjectionInfo);
+ dispatchCallbacks(getRecordedUids(), true /* visibleInScreenRecording*/);
+ }
+ }
+
+ private void onScreenRecordingStop() {
+ synchronized (mWms.mGlobalLock) {
+ dispatchCallbacks(getRecordedUids(), false /*visibleInScreenRecording*/);
+ mRecordedWC = null;
+ }
+ }
+
+ @GuardedBy("WindowManagerService.mGlobalLock")
+ void onProcessActivityVisibilityChanged(int uid, boolean processVisible) {
+ // If recording isn't active or there's no registered callback for the uid, there's nothing
+ // to do on this visibility change.
+ if (mRecordedWC == null || !mLastInvokedStateByUid.containsKey(uid)) {
+ return;
+ }
+
+ // If the callbacks are already in the correct state, avoid making duplicate callbacks for
+ // the same state. This can happen when:
+ // * a process becomes visible but its UID already has a recorded activity from another
+ // process.
+ // * a process becomes invisible but its UID already doesn't have any recorded activities.
+ if (processVisible == mLastInvokedStateByUid.get(uid)) {
+ return;
+ }
+
+ // If the process visibility change doesn't change the visibility of the UID, avoid making
+ // duplicate callbacks for the same state. This can happen when:
+ // * a process becomes visible but the newly visible activity isn't in the recorded window
+ // container.
+ // * a process becomes invisible but there are still activities being recorded for the UID.
+ boolean uidInRecording = uidHasRecordedActivity(uid);
+ if ((processVisible && !uidInRecording) || (!processVisible && uidInRecording)) {
+ return;
+ }
+
+ dispatchCallbacks(Set.of(uid), processVisible);
+ }
+
+ @GuardedBy("WindowManagerService.mGlobalLock")
+ private boolean uidHasRecordedActivity(int uid) {
+ if (mRecordedWC == null) {
+ return false;
+ }
+ boolean[] hasRecordedActivity = {false};
+ mRecordedWC.forAllActivities(activityRecord -> {
+ if (activityRecord.getUid() == uid && activityRecord.isVisibleRequested()) {
+ hasRecordedActivity[0] = true;
+ return true;
+ }
+ return false;
+ }, true /*traverseTopToBottom*/);
+ return hasRecordedActivity[0];
+ }
+
+ @GuardedBy("WindowManagerService.mGlobalLock")
+ private Set<Integer> getRecordedUids() {
+ Set<Integer> result = new ArraySet<>();
+ if (mRecordedWC == null) {
+ return result;
+ }
+ mRecordedWC.forAllActivities(activityRecord -> {
+ if (activityRecord.isVisibleRequested() && mLastInvokedStateByUid.containsKey(
+ activityRecord.getUid())) {
+ result.add(activityRecord.getUid());
+ }
+ }, true /*traverseTopToBottom*/);
+ return result;
+ }
+
+ @GuardedBy("WindowManagerService.mGlobalLock")
+ private void dispatchCallbacks(Set<Integer> uids, boolean visibleInScreenRecording) {
+ if (uids.isEmpty()) {
+ return;
+ }
+
+ for (Integer uid : uids) {
+ mLastInvokedStateByUid.put(uid, visibleInScreenRecording);
+ }
+
+ for (Callback callback : mCallbacks.values()) {
+ if (!uids.contains(callback.mUid)) {
+ continue;
+ }
+ try {
+ callback.mCallback.onScreenRecordingStateChanged(visibleInScreenRecording);
+ } catch (RemoteException e) {
+ // Client has died. Cleanup is handled via DeathRecipient.
+ }
+ }
+ }
+
+ void dump(PrintWriter pw) {
+ pw.format("ScreenRecordingCallbackController:\n");
+ pw.format(" Registered callbacks:\n");
+ for (Map.Entry<IBinder, Callback> entry : mCallbacks.entrySet()) {
+ pw.format(" callback=%s uid=%s\n", entry.getKey(), entry.getValue().mUid);
+ }
+ pw.format(" Last invoked states:\n");
+ for (Map.Entry<Integer, Boolean> entry : mLastInvokedStateByUid.entrySet()) {
+ pw.format(" uid=%s isVisibleInScreenRecording=%s\n", entry.getKey(),
+ entry.getValue());
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/SensitiveContentPackages.java b/services/core/java/com/android/server/wm/SensitiveContentPackages.java
index 3862b82512c3..a7d6903bbe30 100644
--- a/services/core/java/com/android/server/wm/SensitiveContentPackages.java
+++ b/services/core/java/com/android/server/wm/SensitiveContentPackages.java
@@ -21,7 +21,6 @@ import android.util.ArraySet;
import java.io.PrintWriter;
import java.util.Objects;
-import java.util.Set;
/**
* Cache of distinct package/uid pairs that require being blocked from screen capture. This class is
@@ -41,10 +40,33 @@ public class SensitiveContentPackages {
return false;
}
- /** Replaces the set of package/uid pairs to set that should be blocked from screen capture */
- public void setShouldBlockScreenCaptureForApp(@NonNull Set<PackageInfo> packageInfos) {
- mProtectedPackages.clear();
+ /**
+ * Adds the set of package/uid pairs to set that should be blocked from screen capture
+ *
+ * @param packageInfos packages to be blocked
+ * @return {@code true} if packages set is modified, {@code false} otherwise.
+ */
+ public boolean addBlockScreenCaptureForApps(@NonNull ArraySet<PackageInfo> packageInfos) {
+ if (mProtectedPackages.equals(packageInfos)) {
+ // new set is equal to current set of packages, no need to update
+ return false;
+ }
mProtectedPackages.addAll(packageInfos);
+ return true;
+ }
+
+ /**
+ * Clears the set of package/uid pairs that should be blocked from screen capture
+ *
+ * @return {@code true} if packages set is modified, {@code false} otherwise.
+ */
+ public boolean clearBlockedApps() {
+ if (mProtectedPackages.isEmpty()) {
+ // set was already empty
+ return false;
+ }
+ mProtectedPackages.clear();
+ return true;
}
void dump(PrintWriter pw) {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 314d720692f7..8f9ed8353456 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3558,9 +3558,11 @@ class Task extends TaskFragment {
&& top.mLetterboxUiController.isSystemOverrideToFullscreenEnabled();
appCompatTaskInfo.isFromLetterboxDoubleTap = top != null
&& top.mLetterboxUiController.isFromDoubleTap();
- if (appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
+ if (top != null) {
appCompatTaskInfo.topActivityLetterboxWidth = top.getBounds().width();
appCompatTaskInfo.topActivityLetterboxHeight = top.getBounds().height();
+ }
+ if (appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
if (appCompatTaskInfo.isTopActivityPillarboxed()) {
// Pillarboxed
appCompatTaskInfo.topActivityLetterboxHorizontalPosition =
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f620a9743eb4..2accf9a2a43a 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -2841,6 +2841,19 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
}
+ /** Returns {@code true} if the display should use high performance hint for this transition. */
+ boolean shouldUsePerfHint(@NonNull DisplayContent dc) {
+ if (mOverrideOptions != null
+ && mOverrideOptions.getType() == ActivityOptions.ANIM_SCENE_TRANSITION
+ && mType == TRANSIT_TO_BACK && mParticipants.size() == 1) {
+ // This should be from convertFromTranslucent that makes the occluded activity invisible
+ // without animation. So do not use perf hint (especially early-wakeup) that may disturb
+ // SurfaceFlinger scheduling around the last frame.
+ return false;
+ }
+ return mTargetDisplays.contains(dc);
+ }
+
/**
* Returns {@code true} if the transition and the corresponding transaction should be applied
* on display thread. Currently, this only checks for display rotation change because the order
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 708d63e27ec2..59e3350d5c13 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -1237,8 +1237,15 @@ class TransitionController {
// enableHighPerfTransition(true) is also called in Transition#recordDisplay.
for (int i = mAtm.mRootWindowContainer.getChildCount() - 1; i >= 0; i--) {
final DisplayContent dc = mAtm.mRootWindowContainer.getChildAt(i);
- if (isTransitionOnDisplay(dc)) {
+ if (mCollectingTransition != null && mCollectingTransition.shouldUsePerfHint(dc)) {
dc.enableHighPerfTransition(true);
+ continue;
+ }
+ for (int j = mPlayingTransitions.size() - 1; j >= 0; j--) {
+ if (mPlayingTransitions.get(j).shouldUsePerfHint(dc)) {
+ dc.enableHighPerfTransition(true);
+ break;
+ }
}
}
// Usually transitions put quite a load onto the system already (with all the things
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 22b690e85c35..f2a58e54bfbe 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -32,6 +32,7 @@ import android.hardware.display.VirtualDisplayConfig;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Message;
+import android.util.ArraySet;
import android.util.Pair;
import android.view.ContentRecordingSession;
import android.view.Display;
@@ -1020,5 +1021,13 @@ public abstract class WindowManagerInternal {
*
* @param packageInfos set of {@link PackageInfo} whose windows should be blocked from capture
*/
- public abstract void setShouldBlockScreenCaptureForApp(@NonNull Set<PackageInfo> packageInfos);
+ public abstract void addBlockScreenCaptureForApps(@NonNull ArraySet<PackageInfo> packageInfos);
+
+ /**
+ * Clears apps added to collection of apps in which screen capture should be disabled.
+ *
+ * <p> This clears and resets any existing set or added applications from
+ * * {@link #addBlockScreenCaptureForApps(ArraySet)}
+ */
+ public abstract void clearBlockedApps();
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index b38dc008f4e5..6c833565119f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -303,6 +303,7 @@ import android.view.displayhash.VerifiedDisplayHash;
import android.view.inputmethod.ImeTracker;
import android.window.AddToSurfaceSyncGroupResult;
import android.window.ClientWindowFrames;
+import android.window.IScreenRecordingCallback;
import android.window.ISurfaceSyncGroupCompletedListener;
import android.window.ITaskFpsCallback;
import android.window.ITrustedPresentationListener;
@@ -369,7 +370,6 @@ import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
-import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
@@ -1104,6 +1104,8 @@ public class WindowManagerService extends IWindowManager.Stub
void onAppFreezeTimeout();
}
+ private final ScreenRecordingCallbackController mScreenRecordingCallbackController;
+
public static WindowManagerService main(final Context context, final InputManagerService im,
final boolean showBootMsgs, WindowManagerPolicy policy,
ActivityTaskManagerService atm) {
@@ -1345,6 +1347,7 @@ public class WindowManagerService extends IWindowManager.Stub
mBlurController = new BlurController(mContext, mPowerManager);
mTaskFpsCallbackController = new TaskFpsCallbackController(mContext);
mAccessibilityController = new AccessibilityController(this);
+ mScreenRecordingCallbackController = new ScreenRecordingCallbackController(this);
mSystemPerformanceHinter = new SystemPerformanceHinter(mContext, displayId -> {
synchronized (mGlobalLock) {
DisplayContent dc = mRoot.getDisplayContent(displayId);
@@ -7188,6 +7191,7 @@ public class WindowManagerService extends IWindowManager.Stub
mSystemPerformanceHinter.dump(pw, "");
mTrustedPresentationListenerController.dump(pw);
mSensitiveContentPackages.dump(pw);
+ mScreenRecordingCallbackController.dump(pw);
}
}
@@ -8571,10 +8575,23 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public void setShouldBlockScreenCaptureForApp(Set<PackageInfo> packageInfos) {
+ public void addBlockScreenCaptureForApps(ArraySet<PackageInfo> packageInfos) {
+ synchronized (mGlobalLock) {
+ boolean modified =
+ mSensitiveContentPackages.addBlockScreenCaptureForApps(packageInfos);
+ if (modified) {
+ WindowManagerService.this.refreshScreenCaptureDisabled();
+ }
+ }
+ }
+
+ @Override
+ public void clearBlockedApps() {
synchronized (mGlobalLock) {
- mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(packageInfos);
- WindowManagerService.this.refreshScreenCaptureDisabled();
+ boolean modified = mSensitiveContentPackages.clearBlockedApps();
+ if (modified) {
+ WindowManagerService.this.refreshScreenCaptureDisabled();
+ }
}
}
}
@@ -9889,4 +9906,18 @@ public class WindowManagerService extends IWindowManager.Stub
int id) {
mTrustedPresentationListenerController.unregisterListener(listener, id);
}
+
+ @Override
+ public boolean registerScreenRecordingCallback(IScreenRecordingCallback callback) {
+ return mScreenRecordingCallbackController.register(callback);
+ }
+
+ @Override
+ public void unregisterScreenRecordingCallback(IScreenRecordingCallback callback) {
+ mScreenRecordingCallbackController.unregister(callback);
+ }
+
+ void onProcessActivityVisibilityChanged(int uid, boolean visible) {
+ mScreenRecordingCallbackController.onProcessActivityVisibilityChanged(uid, visible);
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index b8fa5e5b2786..6d2e8cc29506 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -1271,8 +1271,10 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
& (ACTIVITY_STATE_FLAG_IS_VISIBLE | ACTIVITY_STATE_FLAG_IS_WINDOW_VISIBLE)) != 0;
if (!wasAnyVisible && anyVisible) {
mAtm.mVisibleActivityProcessTracker.onAnyActivityVisible(this);
+ mAtm.mWindowManager.onProcessActivityVisibilityChanged(mUid, true /*visible*/);
} else if (wasAnyVisible && !anyVisible) {
mAtm.mVisibleActivityProcessTracker.onAllActivitiesInvisible(this);
+ mAtm.mWindowManager.onProcessActivityVisibilityChanged(mUid, false /*visible*/);
} else if (wasAnyVisible && !wasResumed && hasResumedActivity()) {
mAtm.mVisibleActivityProcessTracker.onActivityResumedWhileVisible(this);
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 775570cb08ba..dfa9dcecfbb5 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -37,7 +37,6 @@ cc_library_static {
"com_android_server_adb_AdbDebuggingManager.cpp",
"com_android_server_am_BatteryStatsService.cpp",
"com_android_server_biometrics_SurfaceToNativeHandleConverter.cpp",
- "com_android_server_BootReceiver.cpp",
"com_android_server_ConsumerIrService.cpp",
"com_android_server_companion_virtual_InputController.cpp",
"com_android_server_devicepolicy_CryptoTestHelper.cpp",
@@ -95,16 +94,6 @@ cc_library_static {
header_libs: [
"bionic_libc_platform_headers",
],
-
- static_libs: [
- "libunwindstack",
- ],
-
- whole_static_libs: [
- "libdebuggerd_tombstone_proto_to_text",
- ],
-
- runtime_libs: ["libdexfile"],
}
cc_defaults {
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 15eb7c64a40c..cc08488742b2 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -33,7 +33,3 @@ per-file com_android_server_companion_virtual_InputController.cpp = file:/servic
# Bug component : 158088 = per-file *AnrTimer*
per-file *AnrTimer* = file:/PERFORMANCE_OWNERS
-
-# Bug component : 158088 = per-file com_android_server_utils_AnrTimer*.java
-per-file com_android_server_utils_AnrTimer*.java = file:/PERFORMANCE_OWNERS
-per-file com_android_server_BootReceiver.cpp = file:/STABILITY_OWNERS
diff --git a/services/core/jni/com_android_server_BootReceiver.cpp b/services/core/jni/com_android_server_BootReceiver.cpp
deleted file mode 100644
index 3892d284dafb..000000000000
--- a/services/core/jni/com_android_server_BootReceiver.cpp
+++ /dev/null
@@ -1,57 +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.
- */
-
-#include <libdebuggerd/tombstone.h>
-#include <nativehelper/JNIHelp.h>
-
-#include <sstream>
-
-#include "jni.h"
-#include "tombstone.pb.h"
-
-namespace android {
-
-static void writeToString(std::stringstream& ss, const std::string& line, bool should_log) {
- ss << line << std::endl;
-}
-
-static jstring com_android_server_BootReceiver_getTombstoneText(JNIEnv* env, jobject,
- jbyteArray tombstoneBytes) {
- Tombstone tombstone;
- tombstone.ParseFromArray(env->GetByteArrayElements(tombstoneBytes, 0),
- env->GetArrayLength(tombstoneBytes));
-
- std::stringstream tombstoneString;
-
- tombstone_proto_to_text(tombstone,
- std::bind(&writeToString, std::ref(tombstoneString),
- std::placeholders::_1, std::placeholders::_2));
-
- return env->NewStringUTF(tombstoneString.str().c_str());
-}
-
-static const JNINativeMethod sMethods[] = {
- /* name, signature, funcPtr */
- {"getTombstoneText", "([B)Ljava/lang/String;",
- (jstring*)com_android_server_BootReceiver_getTombstoneText},
-};
-
-int register_com_android_server_BootReceiver(JNIEnv* env) {
- return jniRegisterNativeMethods(env, "com/android/server/BootReceiver", sMethods,
- NELEM(sMethods));
-}
-
-} // namespace android
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 414339d3f349..2b9bb7a4de87 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -685,6 +685,8 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon
{ // acquire lock
std::scoped_lock _l(mLock);
+ outConfig->mousePointerSpeed = mLocked.pointerSpeed;
+ outConfig->mousePointerAccelerationEnabled = mLocked.mousePointerAccelerationEnabled;
outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed
* POINTER_SPEED_EXPONENT);
outConfig->pointerVelocityControlParameters.acceleration =
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index a4b1f841d3bc..5d1eb496903b 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -66,7 +66,6 @@ int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env);
int register_android_server_sensor_SensorService(JavaVM* vm, JNIEnv* env);
int register_android_server_companion_virtual_InputController(JNIEnv* env);
int register_android_server_app_GameManagerService(JNIEnv* env);
-int register_com_android_server_BootReceiver(JNIEnv* env);
int register_com_android_server_wm_TaskFpsCallbackController(JNIEnv* env);
int register_com_android_server_display_DisplayControl(JNIEnv* env);
int register_com_android_server_SystemClockTime(JNIEnv* env);
@@ -129,7 +128,6 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
register_android_server_sensor_SensorService(vm, env);
register_android_server_companion_virtual_InputController(env);
register_android_server_app_GameManagerService(env);
- register_com_android_server_BootReceiver(env);
register_com_android_server_wm_TaskFpsCallbackController(env);
register_com_android_server_display_DisplayControl(env);
register_com_android_server_SystemClockTime(env);
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
index 94caf2865b66..c8a65459d3df 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppIdAppOpPolicy.kt
@@ -17,6 +17,7 @@
package com.android.server.permission.access.appop
import android.app.AppOpsManager
+import android.util.Slog
import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.MutableAccessState
import com.android.server.permission.access.MutateStateScope
@@ -84,6 +85,10 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) {
appOpName: String,
mode: Int
): Boolean {
+ if (userId !in newState.userStates) {
+ Slog.e(LOG_TAG, "Unable to set app op mode for missing user $userId")
+ return false
+ }
val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
val oldMode =
newState.userStates[userId]!!
@@ -152,4 +157,8 @@ class AppIdAppOpPolicy : BaseAppOpPolicy(AppIdAppOpPersistence()) {
*/
abstract fun onStateMutated()
}
+
+ companion object {
+ private val LOG_TAG = AppIdAppOpPolicy::class.java.simpleName
+ }
}
diff --git a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
index 0d9470edc4ea..2f15dc7b232a 100644
--- a/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/PackageAppOpPolicy.kt
@@ -17,6 +17,7 @@
package com.android.server.permission.access.appop
import android.app.AppOpsManager
+import android.util.Slog
import com.android.server.permission.access.GetStateScope
import com.android.server.permission.access.MutableAccessState
import com.android.server.permission.access.MutateStateScope
@@ -87,6 +88,10 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
appOpName: String,
mode: Int
): Boolean {
+ if (userId !in newState.userStates) {
+ Slog.e(LOG_TAG, "Unable to set app op mode for missing user $userId")
+ return false
+ }
val defaultMode = AppOpsManager.opToDefaultMode(appOpName)
val oldMode =
newState.userStates[userId]!!
@@ -155,4 +160,8 @@ class PackageAppOpPolicy : BaseAppOpPolicy(PackageAppOpPersistence()) {
*/
abstract fun onStateMutated()
}
+
+ companion object {
+ private val LOG_TAG = PackageAppOpPolicy::class.java.simpleName
+ }
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 022268df4a63..62d2d7ee848a 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -134,7 +134,9 @@ class AppIdPermissionPolicy : SchemePolicy() {
) {
val changedPermissionNames = MutableIndexedSet<String>()
packageNames.forEachIndexed { _, packageName ->
- val packageState = newState.externalState.packageStates[packageName]!!
+ // The package may still be removed even if it was once notified as installed.
+ val packageState = newState.externalState.packageStates[packageName]
+ ?: return@forEachIndexed
adoptPermissions(packageState, changedPermissionNames)
addPermissionGroups(packageState)
addPermissions(packageState, changedPermissionNames)
@@ -147,12 +149,14 @@ class AppIdPermissionPolicy : SchemePolicy() {
}
packageNames.forEachIndexed { _, packageName ->
- val packageState = newState.externalState.packageStates[packageName]!!
+ val packageState = newState.externalState.packageStates[packageName]
+ ?: return@forEachIndexed
val installedPackageState = if (isSystemUpdated) packageState else null
evaluateAllPermissionStatesForPackage(packageState, installedPackageState)
}
packageNames.forEachIndexed { _, packageName ->
- val packageState = newState.externalState.packageStates[packageName]!!
+ val packageState = newState.externalState.packageStates[packageName]
+ ?: return@forEachIndexed
newState.externalState.userIds.forEachIndexed { _, userId ->
inheritImplicitPermissionStates(packageState.appId, userId)
}
@@ -1607,6 +1611,13 @@ class AppIdPermissionPolicy : SchemePolicy() {
flagMask: Int,
flagValues: Int
): Boolean {
+ if (userId !in newState.userStates) {
+ // Despite that we check UserManagerInternal.exists() in PermissionService, we may still
+ // sometimes get race conditions between that check and the actual mutateState() call.
+ // This should rarely happen but at least we should not crash.
+ Slog.e(LOG_TAG, "Unable to update permission flags for missing user $userId")
+ return false
+ }
val oldFlags =
newState.userStates[userId]!!
.appIdPermissionFlags[appId]
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index 83479e28fe24..dd8c6a23b829 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -295,6 +295,7 @@ public class DisplayModeDirectorTest {
private static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
private Context mContext;
+ private Resources mResources;
private FakesInjector mInjector;
private Handler mHandler;
@Rule
@@ -318,6 +319,8 @@ public class DisplayModeDirectorTest {
@Before
public void setUp() throws Exception {
mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
+ mResources = mockResources();
+ when(mContext.getResources()).thenReturn(mResources);
final MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
when(mContext.getContentResolver()).thenReturn(resolver);
mInjector = spy(new FakesInjector(mDisplayManagerInternalMock, mStatusBarMock,
@@ -325,6 +328,60 @@ public class DisplayModeDirectorTest {
mHandler = new Handler(Looper.getMainLooper());
}
+ private Resources mockResources() {
+ var resources = mock(Resources.class);
+ when(resources.getBoolean(R.bool.config_ignoreUdfpsVote))
+ .thenReturn(false);
+ when(resources.getBoolean(R.bool.config_refreshRateSynchronizationEnabled))
+ .thenReturn(false);
+ when(resources.getBoolean(R.bool.config_supportsDvrr))
+ .thenReturn(false);
+ when(resources.getInteger(R.integer.config_displayWhiteBalanceBrightnessFilterHorizon))
+ .thenReturn(10000);
+ when(resources.getInteger(R.integer.config_defaultPeakRefreshRate))
+ .thenReturn(0);
+ when(resources.getInteger(R.integer.config_externalDisplayPeakRefreshRate))
+ .thenReturn(0);
+ when(resources.getInteger(R.integer.config_externalDisplayPeakWidth))
+ .thenReturn(0);
+ when(resources.getInteger(R.integer.config_externalDisplayPeakHeight))
+ .thenReturn(0);
+ when(resources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
+ .thenReturn(0);
+ when(resources.getInteger(R.integer.config_defaultRefreshRateInZone))
+ .thenReturn(0);
+ when(resources.getInteger(R.integer.config_defaultRefreshRate))
+ .thenReturn(60);
+ when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmHdr))
+ .thenReturn(0);
+ when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmSunlight))
+ .thenReturn(0);
+
+ when(resources.getString(R.string.config_displayLightSensorType))
+ .thenReturn(null);
+
+ when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
+ .thenReturn(new int[]{});
+ when(resources.getIntArray(
+ R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+ .thenReturn(new int[]{});
+ when(resources.getIntArray(
+ R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+ .thenReturn(new int[]{});
+ when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
+ .thenReturn(new int[]{});
+
+ doAnswer(invocation -> {
+ TypedValue value = invocation.getArgument(1);
+ value.type = TypedValue.TYPE_FLOAT;
+ value.data = Float.floatToIntBits(10f);
+ return null; // void method, so return null
+ }).when(resources).getValue(eq(R.dimen.config_displayWhiteBalanceBrightnessFilterIntercept),
+ any(), eq(true));
+
+ return resources;
+ }
+
private DisplayModeDirector createDirectorFromRefreshRateArray(
float[] refreshRates, int baseModeId) {
return createDirectorFromRefreshRateArray(refreshRates, baseModeId, refreshRates[0]);
@@ -2911,31 +2968,31 @@ public class DisplayModeDirectorTest {
@Test
public void testNotifyDefaultDisplayDeviceUpdated() {
- Resources resources = mock(Resources.class);
- when(mContext.getResources()).thenReturn(resources);
- when(resources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
+ when(mResources.getInteger(com.android.internal.R.integer.config_defaultPeakRefreshRate))
.thenReturn(75);
- when(resources.getInteger(R.integer.config_defaultRefreshRate))
+ when(mResources.getInteger(R.integer.config_defaultRefreshRate))
.thenReturn(45);
- when(resources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
+ when(mResources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
.thenReturn(65);
- when(resources.getInteger(R.integer.config_defaultRefreshRateInZone))
+ when(mResources.getInteger(R.integer.config_defaultRefreshRateInZone))
.thenReturn(85);
- when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmHdr))
+ when(mResources.getInteger(R.integer.config_defaultRefreshRateInHbmHdr))
.thenReturn(95);
- when(resources.getInteger(R.integer.config_defaultRefreshRateInHbmSunlight))
+ when(mResources.getInteger(R.integer.config_defaultRefreshRateInHbmSunlight))
.thenReturn(100);
- when(resources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
+ when(mResources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
.thenReturn(new int[]{5});
- when(resources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
+ when(mResources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
.thenReturn(new int[]{10});
when(
- resources.getIntArray(R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
+ mResources.getIntArray(
+ R.array.config_highDisplayBrightnessThresholdsOfFixedRefreshRate))
.thenReturn(new int[]{250});
when(
- resources.getIntArray(R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
+ mResources.getIntArray(
+ R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
.thenReturn(new int[]{7000});
- when(resources.getInteger(
+ when(mResources.getInteger(
com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon))
.thenReturn(3);
ArgumentCaptor<TypedValue> valueArgumentCaptor = ArgumentCaptor.forClass(TypedValue.class);
@@ -2943,7 +3000,7 @@ public class DisplayModeDirectorTest {
valueArgumentCaptor.getValue().type = 4;
valueArgumentCaptor.getValue().data = 13;
return null;
- }).when(resources).getValue(eq(com.android.internal.R.dimen
+ }).when(mResources).getValue(eq(com.android.internal.R.dimen
.config_displayWhiteBalanceBrightnessFilterIntercept),
valueArgumentCaptor.capture(), eq(true));
DisplayModeDirector director =
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
index ff91d34470d4..92016dfc631b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayObserverTest.java
@@ -20,11 +20,10 @@ package com.android.server.display.mode;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.Mode.INVALID_MODE_ID;
-
import static com.android.server.display.mode.DisplayModeDirector.SYNCHRONIZED_REFRESH_RATE_TOLERANCE;
import static com.android.server.display.mode.Vote.PRIORITY_LIMIT_MODE;
-import static com.android.server.display.mode.Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE;
import static com.android.server.display.mode.Vote.PRIORITY_SYNCHRONIZED_REFRESH_RATE;
+import static com.android.server.display.mode.Vote.PRIORITY_USER_SETTING_DISPLAY_PREFERRED_SIZE;
import static com.android.server.display.mode.VotesStorage.GLOBAL_ID;
import static com.google.common.truth.Truth.assertThat;
@@ -43,6 +42,7 @@ import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.Looper;
import android.provider.DeviceConfigInterface;
+import android.test.mock.MockContentResolver;
import android.view.Display;
import android.view.DisplayInfo;
@@ -51,21 +51,26 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
import com.android.server.display.feature.DisplayManagerFlags;
import com.android.server.sensors.SensorManagerInternal;
+import junitparams.JUnitParamsRunner;
+
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import junitparams.JUnitParamsRunner;
-
-
@SmallTest
@RunWith(JUnitParamsRunner.class)
public class DisplayObserverTest {
+ @Rule
+ public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+
private static final int EXTERNAL_DISPLAY = 1;
private static final int MAX_WIDTH = 1920;
private static final int MAX_HEIGHT = 1080;
@@ -120,6 +125,8 @@ public class DisplayObserverTest {
mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
mResources = mock(Resources.class);
when(mContext.getResources()).thenReturn(mResources);
+ MockContentResolver resolver = mSettingsProviderRule.mockContentResolver(mContext);
+ when(mContext.getContentResolver()).thenReturn(resolver);
when(mResources.getInteger(R.integer.config_externalDisplayPeakRefreshRate))
.thenReturn(0);
when(mResources.getInteger(R.integer.config_externalDisplayPeakWidth))
diff --git a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
index b363fd4cc7cb..d78143381ae5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/SensitiveContentProtectionManagerServiceTest.java
@@ -20,11 +20,13 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
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.when;
import android.media.projection.MediaProjectionInfo;
@@ -35,6 +37,7 @@ import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper.RunWithLooper;
+import android.util.ArraySet;
import androidx.test.filters.SmallTest;
@@ -52,7 +55,6 @@ import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import java.util.Collections;
import java.util.Set;
@SmallTest
@@ -68,6 +70,8 @@ public class SensitiveContentProtectionManagerServiceTest {
private static final int NOTIFICATION_UID_1 = 5;
private static final int NOTIFICATION_UID_2 = 6;
+ private static final ArraySet<PackageInfo> EMPTY_SET = new ArraySet<>();
+
@Rule
public final TestableContext mContext =
new TestableContext(getInstrumentation().getTargetContext(), null);
@@ -107,6 +111,9 @@ public class SensitiveContentProtectionManagerServiceTest {
mSensitiveContentProtectionManagerService.mNotificationListener =
spy(mSensitiveContentProtectionManagerService.mNotificationListener);
+ doCallRealMethod()
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .onListenerConnected();
// Setup RankingMap and two possilbe rankings
when(mSensitiveRanking.hasSensitiveContent()).thenReturn(true);
@@ -128,7 +135,7 @@ public class SensitiveContentProtectionManagerServiceTest {
mSensitiveContentProtectionManagerService.onDestroy();
}
- private Set<PackageInfo> setupSensitiveNotification() {
+ private ArraySet<PackageInfo> setupSensitiveNotification() {
// Setup Notification Values
when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
@@ -149,10 +156,11 @@ public class SensitiveContentProtectionManagerServiceTest {
when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
.thenReturn(mNonSensitiveRanking);
- return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1));
+ return new ArraySet<>(
+ Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1)));
}
- private Set<PackageInfo> setupMultipleSensitiveNotificationsFromSamePackageAndUid() {
+ private ArraySet<PackageInfo> setupMultipleSensitiveNotificationsFromSamePackageAndUid() {
// Setup Notification Values
when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
@@ -173,10 +181,11 @@ public class SensitiveContentProtectionManagerServiceTest {
when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
.thenReturn(mSensitiveRanking);
- return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1));
+ return new ArraySet<>(
+ Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1)));
}
- private Set<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentPackage() {
+ private ArraySet<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentPackage() {
// Setup Notification Values
when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
@@ -197,11 +206,12 @@ public class SensitiveContentProtectionManagerServiceTest {
when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
.thenReturn(mSensitiveRanking);
- return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
- new PackageInfo(NOTIFICATION_PKG_2, NOTIFICATION_UID_1));
+ return new ArraySet<>(
+ Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
+ new PackageInfo(NOTIFICATION_PKG_2, NOTIFICATION_UID_1)));
}
- private Set<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentUid() {
+ private ArraySet<PackageInfo> setupMultipleSensitiveNotificationsFromDifferentUid() {
// Setup Notification Values
when(mNotification1.getKey()).thenReturn(NOTIFICATION_KEY_1);
when(mNotification1.getPackageName()).thenReturn(NOTIFICATION_PKG_1);
@@ -222,8 +232,9 @@ public class SensitiveContentProtectionManagerServiceTest {
when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_2)))
.thenReturn(mSensitiveRanking);
- return Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
- new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_2));
+ return new ArraySet<>(
+ Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1),
+ new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_2)));
}
private void setupNoSensitiveNotifications() {
@@ -251,11 +262,11 @@ public class SensitiveContentProtectionManagerServiceTest {
@Test
public void mediaProjectionOnStart_onProjectionStart_setWmBlockedPackages() {
- Set<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+ ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
}
@Test
@@ -264,7 +275,7 @@ public class SensitiveContentProtectionManagerServiceTest {
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
}
@Test
@@ -273,37 +284,37 @@ public class SensitiveContentProtectionManagerServiceTest {
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
}
@Test
public void mediaProjectionOnStart_multipleNotifications_setWmBlockedPackages() {
- Set<PackageInfo> expectedBlockedPackages =
+ ArraySet<PackageInfo> expectedBlockedPackages =
setupMultipleSensitiveNotificationsFromSamePackageAndUid();
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
}
@Test
public void mediaProjectionOnStart_multiplePackages_setWmBlockedPackages() {
- Set<PackageInfo> expectedBlockedPackages =
+ ArraySet<PackageInfo> expectedBlockedPackages =
setupMultipleSensitiveNotificationsFromDifferentPackage();
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
}
@Test
public void mediaProjectionOnStart_multipleUid_setWmBlockedPackages() {
- Set<PackageInfo> expectedBlockedPackages =
+ ArraySet<PackageInfo> expectedBlockedPackages =
setupMultipleSensitiveNotificationsFromDifferentUid();
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
}
@Test
@@ -316,12 +327,12 @@ public class SensitiveContentProtectionManagerServiceTest {
mMediaProjectionCallbackCaptor.getValue().onStop(mediaProjectionInfo);
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ verify(mWindowManager).clearBlockedApps();
}
@Test
public void mediaProjectionOnStart_afterOnStop_onProjectionStart_setWmBlockedPackages() {
- Set<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+ ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
MediaProjectionInfo mediaProjectionInfo = mock(MediaProjectionInfo.class);
mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
@@ -330,7 +341,7 @@ public class SensitiveContentProtectionManagerServiceTest {
mMediaProjectionCallbackCaptor.getValue().onStart(mediaProjectionInfo);
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(expectedBlockedPackages);
+ verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
}
@Test
@@ -341,7 +352,7 @@ public class SensitiveContentProtectionManagerServiceTest {
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
}
@Test
@@ -352,7 +363,7 @@ public class SensitiveContentProtectionManagerServiceTest {
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
}
@Test
@@ -363,7 +374,7 @@ public class SensitiveContentProtectionManagerServiceTest {
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
}
@Test
@@ -376,6 +387,314 @@ public class SensitiveContentProtectionManagerServiceTest {
mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
- verify(mWindowManager).setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnListenerConnected_projectionNotStarted_noop() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+
+ mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
+ public void nlsOnListenerConnected_projectionStopped_noop() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
+ public void nlsOnListenerConnected_projectionStarted_setWmBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
+ }
+
+ @Test
+ public void nlsOnListenerConnected_noSensitiveNotifications_noBlockedPackages() {
+ setupNoSensitiveNotifications();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnListenerConnected_noNotifications_noBlockedPackages() {
+ setupNoNotifications();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnListenerConnected_nullRankingMap_noBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+ doReturn(null)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getCurrentRanking();
+
+ mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnListenerConnected_missingRanking_noBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
+ doReturn(mRankingMap)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getCurrentRanking();
+
+ mSensitiveContentProtectionManagerService.mNotificationListener.onListenerConnected();
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnNotificationRankingUpdate_projectionNotStarted_noop() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationRankingUpdate(mRankingMap);
+
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
+ public void nlsOnNotificationRankingUpdate_projectionStopped_noop() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationRankingUpdate(mRankingMap);
+
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
+ public void nlsOnNotificationRankingUpdate_projectionStarted_setWmBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ ArraySet<PackageInfo> expectedBlockedPackages = setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationRankingUpdate(mRankingMap);
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
+ }
+
+ @Test
+ public void nlsOnNotificationRankingUpdate_noSensitiveNotifications_noBlockedPackages() {
+ setupNoSensitiveNotifications();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationRankingUpdate(mRankingMap);
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnNotificationRankingUpdate_noNotifications_noBlockedPackages() {
+ setupNoNotifications();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationRankingUpdate(mRankingMap);
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnNotificationRankingUpdate_nullRankingMap_noBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationRankingUpdate(null);
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnNotificationRankingUpdate_missingRanking_noBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
+ doReturn(mRankingMap)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getCurrentRanking();
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationRankingUpdate(mRankingMap);
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnNotificationRankingUpdate_getActiveNotificationsThrows_noBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ doThrow(SecurityException.class)
+ .when(mSensitiveContentProtectionManagerService.mNotificationListener)
+ .getActiveNotifications();
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationRankingUpdate(mRankingMap);
+
+ verify(mWindowManager).addBlockScreenCaptureForApps(EMPTY_SET);
+ }
+
+ @Test
+ public void nlsOnNotificationPosted_projectionNotStarted_noop() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationPosted(mNotification1, mRankingMap);
+
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
+ public void nlsOnNotificationPosted_projectionStopped_noop() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ mMediaProjectionCallbackCaptor.getValue().onStop(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationPosted(mNotification1, mRankingMap);
+
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
+ public void nlsOnNotificationPosted_projectionStarted_setWmBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationPosted(mNotification1, mRankingMap);
+
+ ArraySet<PackageInfo> expectedBlockedPackages = new ArraySet<>(
+ Set.of(new PackageInfo(NOTIFICATION_PKG_1, NOTIFICATION_UID_1)));
+ verify(mWindowManager).addBlockScreenCaptureForApps(expectedBlockedPackages);
+ }
+
+ @Test
+ public void nlsOnNotificationPosted_noSensitiveNotifications_noBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationPosted(mNotification2, mRankingMap);
+
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
+ public void nlsOnNotificationPosted_noNotifications_noBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationPosted(null, mRankingMap);
+
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
+ public void nlsOnNotificationPosted_nullRankingMap_noBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationPosted(mNotification1, null);
+
+ verifyZeroInteractions(mWindowManager);
+ }
+
+ @Test
+ public void nlsOnNotificationPosted_missingRanking_noBlockedPackages() {
+ // Sets up mNotification1 & mRankingMap to be a sensitive notification, and mNotification2
+ // as non-sensitive
+ setupSensitiveNotification();
+ mMediaProjectionCallbackCaptor.getValue().onStart(mock(MediaProjectionInfo.class));
+ Mockito.reset(mWindowManager);
+ when(mRankingMap.getRawRankingObject(eq(NOTIFICATION_KEY_1))).thenReturn(null);
+
+ mSensitiveContentProtectionManagerService.mNotificationListener
+ .onNotificationPosted(mNotification1, mRankingMap);
+
+ verifyZeroInteractions(mWindowManager);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
new file mode 100644
index 000000000000..7d3a1103a044
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ApplicationStartInfoTest.java
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.server.am.ActivityManagerService.Injector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import android.app.ApplicationStartInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManagerInternal;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.platform.test.annotations.Presubmit;
+import android.text.TextUtils;
+
+import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
+import com.android.server.appop.AppOpsService;
+import com.android.server.wm.ActivityTaskManagerService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+
+/**
+ * Test class for {@link android.app.ApplicationStartInfo}.
+ *
+ * Build/Install/Run:
+ * atest ApplicationStartInfoTest
+ */
+@Presubmit
+public class ApplicationStartInfoTest {
+
+ private static final String TAG = ApplicationStartInfoTest.class.getSimpleName();
+ private static final ComponentName COMPONENT = new ComponentName("com.android.test", ".Foo");
+
+ @Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
+ @Mock private AppOpsService mAppOpsService;
+ @Mock private PackageManagerInternal mPackageManagerInt;
+
+ private Context mContext = getInstrumentation().getTargetContext();
+ private TestInjector mInjector;
+ private ActivityManagerService mAms;
+ private ProcessList mProcessList;
+ private AppStartInfoTracker mAppStartInfoTracker;
+ private Handler mHandler;
+ private HandlerThread mHandlerThread;
+
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mHandlerThread = new HandlerThread(TAG);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+ mProcessList = spy(new ProcessList());
+ mAppStartInfoTracker = spy(new AppStartInfoTracker());
+ mAppStartInfoTracker.mEnabled = true;
+ setFieldValue(ProcessList.class, mProcessList, "mAppStartInfoTracker",
+ mAppStartInfoTracker);
+ mInjector = new TestInjector(mContext);
+ mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread());
+ mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
+ mAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
+ mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal());
+ mAms.mPackageManagerInt = mPackageManagerInt;
+ mAppStartInfoTracker.mService = mAms;
+ doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
+ doReturn("com.android.test").when(mPackageManagerInt).getNameForUid(anyInt());
+ // Remove stale instance of PackageManagerInternal if there is any
+ LocalServices.removeServiceForTest(PackageManagerInternal.class);
+ LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
+ }
+
+ @After
+ public void tearDown() {
+ mHandlerThread.quit();
+ }
+
+ @Test
+ public void testApplicationStartInfo() throws Exception {
+ mAppStartInfoTracker.clearProcessStartInfo(true);
+ mAppStartInfoTracker.mAppStartInfoLoaded.set(true);
+ mAppStartInfoTracker.mAppStartInfoHistoryListSize =
+ mAppStartInfoTracker.APP_START_INFO_HISTORY_LIST_SIZE;
+ mAppStartInfoTracker.mProcStartStoreDir = new File(mContext.getFilesDir(),
+ AppStartInfoTracker.APP_START_STORE_DIR);
+ assertTrue(FileUtils.createDir(mAppStartInfoTracker.mProcStartStoreDir));
+ mAppStartInfoTracker.mProcStartInfoFile = new File(mAppStartInfoTracker.mProcStartStoreDir,
+ AppStartInfoTracker.APP_START_INFO_FILE);
+
+ doNothing().when(mAppStartInfoTracker).schedulePersistProcessStartInfo(anyBoolean());
+
+ final int app1Uid = 10123;
+ final int app1Pid1 = 12345;
+ final int app1Pid2 = 12346;
+ final int app1DefiningUid = 23456;
+ final int app1UidUser2 = 1010123;
+ final int app1PidUser2 = 12347;
+ final String app1ProcessName = "com.android.test.stub1:process";
+ final String app1PackageName = "com.android.test.stub1";
+ final long appStartTimestampIntentStarted = 1000000;
+ final long appStartTimestampActivityLaunchFinished = 2000000;
+ final long appStartTimestampReportFullyDrawn = 3000000;
+ final long appStartTimestampService = 4000000;
+ final long appStartTimestampBroadcast = 5000000;
+ final long appStartTimestampRContentProvider = 6000000;
+
+ ProcessRecord app = makeProcessRecord(
+ app1Pid1, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ app1PackageName); // packageName
+
+ ArrayList<ApplicationStartInfo> list = new ArrayList<ApplicationStartInfo>();
+
+ // Case 1: Activity start intent failed
+ mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT),
+ appStartTimestampIntentStarted);
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+ verifyInProgressRecordsSize(1);
+ assertEquals(list.size(), 0);
+
+ verifyInProgApplicationStartInfo(
+ 0, // index
+ 0, // pid
+ 0, // uid
+ 0, // packageUid
+ null, // definingUid
+ null, // processName
+ ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason
+ ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
+ ApplicationStartInfo.START_TYPE_UNSET, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ mAppStartInfoTracker.onIntentFailed(appStartTimestampIntentStarted);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+ verifyInProgressRecordsSize(0);
+ assertEquals(list.size(), 0);
+
+ mAppStartInfoTracker.clearProcessStartInfo(true);
+
+ // Case 2: Activity start launch cancelled
+ mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT),
+ appStartTimestampIntentStarted);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+ verifyInProgressRecordsSize(1);
+ assertEquals(list.size(), 0);
+
+ mAppStartInfoTracker.onActivityLaunched(appStartTimestampIntentStarted, COMPONENT,
+ ApplicationStartInfo.START_TYPE_COLD, app);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+ verifyInProgressRecordsSize(1);
+ assertEquals(list.size(), 1);
+
+ verifyInProgApplicationStartInfo(
+ 0, // index
+ app1Pid1, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason
+ ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
+ ApplicationStartInfo.START_TYPE_COLD, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ mAppStartInfoTracker.onActivityLaunchCancelled(appStartTimestampIntentStarted);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+ verifyInProgressRecordsSize(0);
+ assertEquals(list.size(), 1);
+
+ verifyApplicationStartInfo(
+ list.get(0), // info
+ app1Pid1, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason
+ ApplicationStartInfo.STARTUP_STATE_ERROR, // startup state
+ ApplicationStartInfo.START_TYPE_COLD, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ mAppStartInfoTracker.clearProcessStartInfo(true);
+
+ // Case 3: Activity start success
+ mAppStartInfoTracker.onIntentStarted(buildIntent(COMPONENT),
+ appStartTimestampIntentStarted);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+ verifyInProgressRecordsSize(1);
+ assertEquals(list.size(), 0);
+
+ mAppStartInfoTracker.onActivityLaunched(appStartTimestampIntentStarted, COMPONENT,
+ ApplicationStartInfo.START_TYPE_COLD, app);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+ verifyInProgressRecordsSize(1);
+ assertEquals(list.size(), 1);
+
+ verifyInProgApplicationStartInfo(
+ 0, // index
+ app1Pid1, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason
+ ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
+ ApplicationStartInfo.START_TYPE_COLD, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ verifyApplicationStartInfo(
+ list.get(0), // info
+ app1Pid1, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason
+ ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
+ ApplicationStartInfo.START_TYPE_COLD, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ mAppStartInfoTracker.onActivityLaunchFinished(appStartTimestampIntentStarted, COMPONENT,
+ appStartTimestampActivityLaunchFinished, ApplicationStartInfo.LAUNCH_MODE_STANDARD);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+ verifyInProgressRecordsSize(1);
+ assertEquals(list.size(), 1);
+
+ verifyInProgApplicationStartInfo(
+ 0, // index
+ app1Pid1, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason
+ ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN, // startup state
+ ApplicationStartInfo.START_TYPE_COLD, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ mAppStartInfoTracker.onReportFullyDrawn(appStartTimestampIntentStarted,
+ appStartTimestampReportFullyDrawn);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
+ verifyInProgressRecordsSize(0);
+ assertEquals(list.size(), 1);
+
+ verifyApplicationStartInfo(
+ list.get(0), // info
+ app1Pid1, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ ApplicationStartInfo.START_REASON_START_ACTIVITY, // reason
+ ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN, // startup state
+ ApplicationStartInfo.START_TYPE_COLD, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ // Don't clear records for use in subsequent cases.
+
+ // Case 4: Create an other app1 record with different pid started for a service
+ sleep(1);
+ app = makeProcessRecord(
+ app1Pid2, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ app1DefiningUid, // definingUid
+ app1ProcessName, // processName
+ app1PackageName); // packageName
+ ServiceRecord service = ServiceRecord.newEmptyInstanceForTest(mAms);
+
+ mAppStartInfoTracker.handleProcessServiceStart(appStartTimestampService, app, service,
+ false);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, 0, 0, list);
+ assertEquals(list.size(), 2);
+
+ verifyApplicationStartInfo(
+ list.get(0), // info
+ app1Pid2, // pid
+ app1Uid, // uid
+ app1Uid, // packageUid
+ app1DefiningUid, // definingUid
+ app1ProcessName, // processName
+ ApplicationStartInfo.START_REASON_SERVICE, // reason
+ ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
+ ApplicationStartInfo.START_TYPE_WARM, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ // Case 5: Create an instance of app1 with a different user started for a broadcast
+ sleep(1);
+ app = makeProcessRecord(
+ app1PidUser2, // pid
+ app1UidUser2, // uid
+ app1UidUser2, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ app1PackageName); // packageName
+
+ mAppStartInfoTracker.handleProcessBroadcastStart(appStartTimestampBroadcast, app,
+ null, true /* isColdStart */);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list);
+ assertEquals(list.size(), 1);
+
+ verifyApplicationStartInfo(
+ list.get(0), // info
+ app1PidUser2, // pid
+ app1UidUser2, // uid
+ app1UidUser2, // packageUid
+ null, // definingUid
+ app1ProcessName, // processName
+ ApplicationStartInfo.START_REASON_BROADCAST, // reason
+ ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
+ ApplicationStartInfo.START_TYPE_COLD, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ // Case 6: User 2 gets removed
+ mAppStartInfoTracker.onPackageRemoved(app1PackageName, app1UidUser2, false);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list);
+ assertEquals(list.size(), 0);
+
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app1PackageName, app1Uid, app1PidUser2, 0, list);
+ assertEquals(list.size(), 2);
+
+
+ // Case 7: Create a process from another package started for a content provider
+ final int app2UidUser2 = 1010234;
+ final int app2PidUser2 = 12348;
+ final String app2ProcessName = "com.android.test.stub2:process";
+ final String app2PackageName = "com.android.test.stub2";
+
+ sleep(1);
+
+ app = makeProcessRecord(
+ app2PidUser2, // pid
+ app2UidUser2, // uid
+ app2UidUser2, // packageUid
+ null, // definingUid
+ app2ProcessName, // processName
+ app2PackageName); // packageName
+
+ mAppStartInfoTracker.handleProcessContentProviderStart(appStartTimestampRContentProvider,
+ app, false);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(app2PackageName, app2UidUser2, app2PidUser2, 0, list);
+ assertEquals(list.size(), 1);
+
+ verifyApplicationStartInfo(
+ list.get(0), // info
+ app2PidUser2, // pid
+ app2UidUser2, // uid
+ app2UidUser2, // packageUid
+ null, // definingUid
+ app2ProcessName, // processName
+ ApplicationStartInfo.START_REASON_CONTENT_PROVIDER, // reason
+ ApplicationStartInfo.STARTUP_STATE_STARTED, // startup state
+ ApplicationStartInfo.START_TYPE_WARM, // state type
+ ApplicationStartInfo.LAUNCH_MODE_STANDARD); // launch mode
+
+ // Case 8: Save and load again
+ ArrayList<ApplicationStartInfo> original = new ArrayList<ApplicationStartInfo>();
+ mAppStartInfoTracker.getStartInfo(null, app1Uid, 0, 0, original);
+ assertTrue(original.size() > 0);
+
+ mAppStartInfoTracker.persistProcessStartInfo();
+ assertTrue(mAppStartInfoTracker.mProcStartInfoFile.exists());
+
+ mAppStartInfoTracker.clearProcessStartInfo(false);
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(null, app1Uid, 0, 0, list);
+ assertEquals(0, list.size());
+
+ mAppStartInfoTracker.loadExistingProcessStartInfo();
+ list.clear();
+ mAppStartInfoTracker.getStartInfo(null, app1Uid, 0, 0, list);
+ assertEquals(original.size(), list.size());
+
+ for (int i = list.size() - 1; i >= 0; i--) {
+ assertTrue(list.get(i).equals(original.get(i)));
+ }
+ }
+
+ private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
+ try {
+ Field field = clazz.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ Field mfield = Field.class.getDeclaredField("accessFlags");
+ mfield.setAccessible(true);
+ mfield.setInt(field, mfield.getInt(field) & ~(Modifier.FINAL | Modifier.PRIVATE));
+ field.set(obj, val);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ }
+ }
+
+ private void sleep(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ }
+ }
+
+ private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
+ String processName, String packageName) {
+ return makeProcessRecord(pid, uid, packageUid, definingUid, processName, packageName, mAms);
+ }
+
+ @SuppressWarnings("GuardedBy")
+ static ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
+ String processName, String packageName, ActivityManagerService ams) {
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = packageName;
+ ProcessRecord app = new ProcessRecord(ams, ai, processName, uid);
+ app.setPid(pid);
+ app.info.uid = packageUid;
+ if (definingUid != null) {
+ app.setHostingRecord(HostingRecord.byAppZygote(COMPONENT, "", definingUid, ""));
+ }
+ return app;
+ }
+
+ private static Intent buildIntent(ComponentName componentName) throws Exception {
+ Intent intent = new Intent();
+ intent.setComponent(componentName);
+ intent.setPackage(componentName.getPackageName());
+ return intent;
+ }
+
+ private void verifyInProgressRecordsSize(int expectedSize) {
+ synchronized (mAppStartInfoTracker.mLock) {
+ assertEquals(mAppStartInfoTracker.mInProgRecords.size(), expectedSize);
+ }
+ }
+
+ private void verifyInProgApplicationStartInfo(int index,
+ Integer pid, Integer uid, Integer packageUid,
+ Integer definingUid, String processName,
+ Integer reason, Integer startupState, Integer startType, Integer launchMode) {
+ synchronized (mAppStartInfoTracker.mLock) {
+ verifyApplicationStartInfo(mAppStartInfoTracker.mInProgRecords.valueAt(index),
+ pid, uid, packageUid, definingUid, processName, reason, startupState,
+ startType, launchMode);
+ }
+ }
+
+ private void verifyApplicationStartInfo(ApplicationStartInfo info,
+ Integer pid, Integer uid, Integer packageUid,
+ Integer definingUid, String processName,
+ Integer reason, Integer startupState, Integer startType, Integer launchMode) {
+ assertNotNull(info);
+
+ if (pid != null) {
+ assertEquals(pid.intValue(), info.getPid());
+ }
+ if (uid != null) {
+ assertEquals(uid.intValue(), info.getRealUid());
+ }
+ if (packageUid != null) {
+ assertEquals(packageUid.intValue(), info.getPackageUid());
+ }
+ if (definingUid != null) {
+ assertEquals(definingUid.intValue(), info.getDefiningUid());
+ }
+ if (processName != null) {
+ assertTrue(TextUtils.equals(processName, info.getProcessName()));
+ }
+ if (reason != null) {
+ assertEquals(reason.intValue(), info.getReason());
+ }
+ if (startupState != null) {
+ assertEquals(startupState.intValue(), info.getStartupState());
+ }
+ if (startType != null) {
+ assertEquals(startType.intValue(), info.getStartType());
+ }
+ if (launchMode != null) {
+ assertEquals(launchMode.intValue(), info.getLaunchMode());
+ }
+ }
+
+ private class TestInjector extends Injector {
+ TestInjector(Context context) {
+ super(context);
+ }
+
+ @Override
+ public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
+ Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandler;
+ }
+
+ @Override
+ public ProcessList getProcessList(ActivityManagerService service) {
+ return mProcessList;
+ }
+ }
+
+ static class ServiceThreadRule implements TestRule {
+
+ private ServiceThread mThread;
+
+ ServiceThread getThread() {
+ return mThread;
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ mThread = new ServiceThread("TestServiceThread",
+ Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */);
+ mThread.start();
+ try {
+ base.evaluate();
+ } finally {
+ mThread.getThreadHandler().runWithScissors(mThread::quit, 0 /* timeout */);
+ }
+ }
+ };
+ }
+ }
+
+ // TODO: [b/302724778] Remove manual JNI load
+ static {
+ System.loadLibrary("mockingservicestestjni");
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 116d5db45023..284e491f8081 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -426,6 +426,8 @@ public class FlexibilityControllerTest {
@Test
public void testGetNextConstraintDropTimeElapsedLocked() {
+ setDeviceConfigLong(KEY_FALLBACK_FLEXIBILITY_DEADLINE, 200 * HOUR_IN_MILLIS);
+
long nextTimeToDropNumConstraints;
// no delay, deadline
@@ -457,15 +459,18 @@ public class FlexibilityControllerTest {
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(130400100, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) / 2,
+ nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(1);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(156320100L, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) * 6 / 10,
+ nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(2);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(182240100L, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + 800000L + (200 * HOUR_IN_MILLIS) * 7 / 10,
+ nextTimeToDropNumConstraints);
// no delay, no deadline
jb = createJob(0);
@@ -473,15 +478,15 @@ public class FlexibilityControllerTest {
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(129600100, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) / 2, nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(1);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(155520100L, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) * 6 / 10, nextTimeToDropNumConstraints);
js.setNumDroppedFlexibleConstraints(2);
nextTimeToDropNumConstraints = mFlexibilityController
.getNextConstraintDropTimeElapsedLocked(js);
- assertEquals(181440100L, nextTimeToDropNumConstraints);
+ assertEquals(FROZEN_TIME + (200 * HOUR_IN_MILLIS) * 7 / 10, nextTimeToDropNumConstraints);
// delay, deadline
jb = createJob(0)
@@ -950,8 +955,7 @@ public class FlexibilityControllerTest {
mJobStore.add(js);
// Needed because if before and after Uid bias is the same, nothing happens.
- when(mJobSchedulerService.getUidBias(mSourceUid))
- .thenReturn(JobInfo.BIAS_DEFAULT);
+ doReturn(JobInfo.BIAS_DEFAULT).when(mJobSchedulerService).getUidBias(mSourceUid);
synchronized (mFlexibilityController.mLock) {
mFlexibilityController.maybeStartTrackingJobLocked(js, null);
@@ -1328,7 +1332,7 @@ public class FlexibilityControllerTest {
final ArraySet<String> pkgs = new ArraySet<>();
pkgs.add(js.getSourcePackageName());
- when(mJobSchedulerService.getPackagesForUidLocked(mSourceUid)).thenReturn(pkgs);
+ doReturn(pkgs).when(mJobSchedulerService).getPackagesForUidLocked(mSourceUid);
setUidBias(mSourceUid, BIAS_TOP_APP);
setUidBias(mSourceUid, BIAS_FOREGROUND_SERVICE);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index 0403c64fc624..ec7e35982311 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -233,7 +233,7 @@ public class PackageArchiverTest {
Exception e = assertThrows(
SecurityException.class,
() -> mArchiveManager.requestArchive(PACKAGE, "different", mIntentSender,
- UserHandle.CURRENT, 0));
+ UserHandle.CURRENT));
assertThat(e).hasMessageThat().isEqualTo(
String.format(
"The UID %s of callerPackageName set by the caller doesn't match the "
@@ -250,7 +250,7 @@ public class PackageArchiverTest {
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
- UserHandle.CURRENT, 0));
+ UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
String.format("Package %s not found.", PACKAGE));
@@ -260,8 +260,7 @@ public class PackageArchiverTest {
public void archiveApp_packageNotInstalledForUser() throws IntentSender.SendIntentException {
mPackageSetting.modifyUserState(UserHandle.CURRENT.getIdentifier()).setInstalled(false);
- mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
- 0);
+ mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
rule.mocks().getHandler().flush();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -291,7 +290,7 @@ public class PackageArchiverTest {
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
- UserHandle.CURRENT, 0));
+ UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo("No installer found");
}
@@ -305,7 +304,7 @@ public class PackageArchiverTest {
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
- UserHandle.CURRENT, 0));
+ UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
"Installer does not support unarchival");
@@ -319,7 +318,7 @@ public class PackageArchiverTest {
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
- UserHandle.CURRENT, 0));
+ UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
TextUtils.formatSimple("The app %s does not have a main activity.", PACKAGE));
@@ -331,8 +330,7 @@ public class PackageArchiverTest {
doThrow(e).when(mArchiveManager).storeIcon(eq(PACKAGE),
any(LauncherActivityInfo.class), eq(mUserId), anyInt(), anyInt());
- mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
- 0);
+ mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
rule.mocks().getHandler().flush();
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -355,7 +353,7 @@ public class PackageArchiverTest {
Exception e = assertThrows(
ParcelableException.class,
() -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
- UserHandle.CURRENT, 0));
+ UserHandle.CURRENT));
assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
assertThat(e.getCause()).hasMessageThat().isEqualTo(
TextUtils.formatSimple("The app %s is opted out of archiving.", PACKAGE));
@@ -363,8 +361,7 @@ public class PackageArchiverTest {
@Test
public void archiveApp_withNoAdditionalFlags_success() {
- mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
- 0);
+ mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
rule.mocks().getHandler().flush();
verify(mInstallerService).uninstall(
@@ -386,14 +383,13 @@ public class PackageArchiverTest {
@Test
public void archiveApp_withAdditionalFlags_success() {
- mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT,
- PackageManager.DELETE_SHOW_DIALOG);
+ mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
rule.mocks().getHandler().flush();
verify(mInstallerService).uninstall(
eq(new VersionedPackage(PACKAGE, PackageManager.VERSION_CODE_HIGHEST)),
eq(CALLER_PACKAGE),
- eq(DELETE_ARCHIVE | DELETE_KEEP_DATA | PackageManager.DELETE_SHOW_DIALOG),
+ eq(DELETE_ARCHIVE | DELETE_KEEP_DATA),
eq(mIntentSender),
eq(UserHandle.CURRENT.getIdentifier()), anyInt());
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index fd6aa0c1ffab..e6298eeccafb 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -19,9 +19,11 @@ import static android.os.UserManager.DISALLOW_OUTGOING_CALLS;
import static android.os.UserManager.DISALLOW_SMS;
import static android.os.UserManager.DISALLOW_USER_SWITCH;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
+import static android.os.UserManager.USER_TYPE_PROFILE_PRIVATE;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -30,20 +32,27 @@ import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import android.annotation.UserIdInt;
import android.app.ActivityManagerInternal;
+import android.app.KeyguardManager;
import android.content.Context;
import android.content.pm.PackageManagerInternal;
import android.content.pm.UserInfo;
+import android.multiuser.Flags;
+import android.os.PowerManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.util.Log;
import android.util.Pair;
@@ -52,6 +61,7 @@ import android.util.Xml;
import androidx.test.annotation.UiThreadTest;
+import com.android.dx.mockito.inline.extended.MockedVoidMethod;
import com.android.internal.widget.LockSettingsInternal;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.testing.ExtendedMockitoRule;
@@ -65,6 +75,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -115,8 +126,11 @@ public final class UserManagerServiceTest {
.spyStatic(LocalServices.class)
.spyStatic(SystemProperties.class)
.mockStatic(Settings.Global.class)
+ .mockStatic(Settings.Secure.class)
.build();
+ @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
private final Object mPackagesLock = new Object();
private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
.getTargetContext();
@@ -133,6 +147,8 @@ public final class UserManagerServiceTest {
private @Mock StorageManager mStorageManager;
private @Mock LockSettingsInternal mLockSettingsInternal;
private @Mock PackageManagerInternal mPackageManagerInternal;
+ private @Mock KeyguardManager mKeyguardManager;
+ private @Mock PowerManager mPowerManager;
/**
* Reference to the {@link UserManagerService} being tested.
@@ -156,6 +172,8 @@ public final class UserManagerServiceTest {
when(mDeviceStorageMonitorInternal.isMemoryLow()).thenReturn(false);
mockGetLocalService(DeviceStorageMonitorInternal.class, mDeviceStorageMonitorInternal);
when(mSpiedContext.getSystemService(StorageManager.class)).thenReturn(mStorageManager);
+ when(mSpiedContext.getSystemService(KeyguardManager.class)).thenReturn(mKeyguardManager);
+ when(mSpiedContext.getSystemService(PowerManager.class)).thenReturn(mPowerManager);
mockGetLocalService(LockSettingsInternal.class, mLockSettingsInternal);
mockGetLocalService(PackageManagerInternal.class, mPackageManagerInternal);
doNothing().when(mSpiedContext).sendBroadcastAsUser(any(), any(), any());
@@ -550,6 +568,143 @@ public final class UserManagerServiceTest {
assertTrue(hasRestrictionsInUserXMLFile(user.id));
}
+ @Test
+ public void testAutoLockOnDeviceLockForPrivateProfile() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ UserManagerService mSpiedUms = spy(mUms);
+ UserInfo privateProfileUser =
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+ mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
+ Mockito.doNothing().when(mSpiedUms).setQuietModeEnabledAsync(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true), any(),
+ any());
+
+ mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true);
+
+ Mockito.verify(mSpiedUms).setQuietModeEnabledAsync(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true),
+ any(), any());
+ }
+
+ @Test
+ public void testAutoLockOnDeviceLockForPrivateProfile_keyguardUnlocked() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ UserManagerService mSpiedUms = spy(mUms);
+ UserInfo privateProfileUser =
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+ mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
+
+ mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(false);
+
+ // Verify that no operation to disable quiet mode is not called
+ Mockito.verify(mSpiedUms, never()).setQuietModeEnabledAsync(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true),
+ any(), any());
+ }
+
+ @Test
+ public void testAutoLockOnDeviceLockForPrivateProfile_flagDisabled() {
+ mSetFlagsRule.disableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ UserManagerService mSpiedUms = spy(mUms);
+ UserInfo privateProfileUser =
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+
+ mSpiedUms.tryAutoLockingPrivateSpaceOnKeyguardChanged(true);
+
+ // Verify that no auto-lock operations take place
+ verify((MockedVoidMethod) () -> Settings.Secure.getInt(any(),
+ eq(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK), anyInt()), never());
+ Mockito.verify(mSpiedUms, never()).setQuietModeEnabledAsync(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), eq(true),
+ any(), any());
+ }
+
+ @Test
+ public void testAutoLockAfterInactityForPrivateProfile() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ UserManagerService mSpiedUms = spy(mUms);
+ mockAutoLockForPrivateSpace(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
+ when(mPowerManager.isInteractive()).thenReturn(false);
+
+ UserInfo privateProfileUser =
+ mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+ Mockito.doNothing().when(mSpiedUms).scheduleMessageToAutoLockPrivateSpace(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), any(),
+ anyLong());
+
+
+ mSpiedUms.maybeScheduleMessageToAutoLockPrivateSpace();
+
+ Mockito.verify(mSpiedUms).scheduleMessageToAutoLockPrivateSpace(
+ eq(privateProfileUser.getUserHandle().getIdentifier()), any(), anyLong());
+ }
+
+ @Test
+ public void testSetOrUpdateAutoLockPreference_noPrivateProfile() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+
+ mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
+
+ Mockito.verify(mSpiedContext, never()).registerReceiver(any(), any(), any(), any());
+ Mockito.verify(mSpiedContext, never()).unregisterReceiver(any());
+ Mockito.verify(mKeyguardManager, never()).removeKeyguardLockedStateListener((any()));
+ Mockito.verify(mKeyguardManager, never()).addKeyguardLockedStateListener(any(), any());
+ }
+
+ @Test
+ public void testSetOrUpdateAutoLockPreference() {
+ mSetFlagsRule.enableFlags(Flags.FLAG_SUPPORT_AUTOLOCK_FOR_PRIVATE_SPACE);
+ mUms.createProfileForUserEvenWhenDisallowedWithThrow("TestPrivateProfile",
+ USER_TYPE_PROFILE_PRIVATE, 0, 0, null);
+
+ // Set the preference to auto lock on device lock
+ mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_ON_DEVICE_LOCK);
+
+ // Verify that keyguard state listener was added
+ Mockito.verify(mKeyguardManager).addKeyguardLockedStateListener(any(), any());
+ //Verity that keyguard state listener was not removed
+ Mockito.verify(mKeyguardManager, never()).removeKeyguardLockedStateListener(any());
+ // Broadcasts are already unregistered when UserManagerService starts and the flag
+ // isDeviceInactivityBroadcastReceiverRegistered is false
+ Mockito.verify(mSpiedContext, never()).registerReceiver(any(), any(), any(), any());
+ Mockito.verify(mSpiedContext, never()).unregisterReceiver(any());
+
+ Mockito.clearInvocations(mKeyguardManager);
+ Mockito.clearInvocations(mSpiedContext);
+
+ // Now set the preference to auto-lock on inactivity
+ mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_INACTIVITY);
+
+ // Verify that inactivity broadcasts are registered
+ Mockito.verify(mSpiedContext, times(2)).registerReceiver(any(), any(), any(), any());
+ // Verify that keyguard state listener is removed
+ Mockito.verify(mKeyguardManager).removeKeyguardLockedStateListener(any());
+ // Verify that all other operations don't take place
+ Mockito.verify(mSpiedContext, never()).unregisterReceiver(any());
+ Mockito.verify(mKeyguardManager, never()).addKeyguardLockedStateListener(any(), any());
+
+ Mockito.clearInvocations(mKeyguardManager);
+ Mockito.clearInvocations(mSpiedContext);
+
+ // Finally, set the preference to don't auto-lock
+ mUms.setOrUpdateAutoLockPreferenceForPrivateProfile(
+ Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_NEVER);
+
+ // Verify that inactivity broadcasts are unregistered and keyguard listener was removed
+ Mockito.verify(mSpiedContext).unregisterReceiver(any());
+ Mockito.verify(mKeyguardManager).removeKeyguardLockedStateListener(any());
+ // Verify that no broadcasts were registered and no listeners were added
+ Mockito.verify(mSpiedContext, never()).registerReceiver(any(), any(), any(), any());
+ Mockito.verify(mKeyguardManager, never()).addKeyguardLockedStateListener(any(), any());
+ }
+
/**
* Returns true if the user's XML file has Default restrictions
* @param userId Id of the user.
@@ -632,6 +787,12 @@ public final class UserManagerServiceTest {
SystemProperties.getBoolean(eq("fw.show_multiuserui"), anyBoolean()));
}
+ private void mockAutoLockForPrivateSpace(int val) {
+ doReturn(val).when(() ->
+ Settings.Secure.getIntForUser(any(), eq(Settings.Secure.PRIVATE_SPACE_AUTO_LOCK),
+ anyInt(), anyInt()));
+ }
+
private void mockCurrentUser(@UserIdInt int userId) {
mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index 37ca09d9fa27..b41568298dbc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -47,6 +47,10 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.SensorProperties;
+import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.fingerprint.FingerprintManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
@@ -69,6 +73,7 @@ import android.view.WindowManagerGlobal;
import androidx.test.core.app.ApplicationProvider;
import com.android.internal.widget.LockPatternUtils;
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -105,6 +110,8 @@ public class TrustManagerServiceTest {
private static final String URI_SCHEME_PACKAGE = "package";
private static final int TEST_USER_ID = 50;
+ private static final UserInfo TEST_USER =
+ new UserInfo(TEST_USER_ID, "user", UserInfo.FLAG_FULL);
private static final int PARENT_USER_ID = 60;
private static final int PROFILE_USER_ID = 70;
private static final long[] PARENT_BIOMETRIC_SIDS = new long[] { 600L, 601L };
@@ -117,6 +124,8 @@ public class TrustManagerServiceTest {
private @Mock ActivityManager mActivityManager;
private @Mock BiometricManager mBiometricManager;
private @Mock DevicePolicyManager mDevicePolicyManager;
+ private @Mock FaceManager mFaceManager;
+ private @Mock FingerprintManager mFingerprintManager;
private @Mock IKeystoreAuthorization mKeystoreAuthorization;
private @Mock LockPatternUtils mLockPatternUtils;
private @Mock PackageManager mPackageManager;
@@ -133,6 +142,9 @@ public class TrustManagerServiceTest {
when(mActivityManager.isUserRunning(TEST_USER_ID)).thenReturn(true);
doReturn(mock(IActivityManager.class)).when(() -> ActivityManager.getService());
+ when(mFaceManager.getSensorProperties()).thenReturn(List.of());
+ when(mFingerprintManager.getSensorProperties()).thenReturn(List.of());
+
doReturn(mKeystoreAuthorization).when(() -> Authorization.getService());
when(mLockPatternUtils.getDevicePolicyManager()).thenReturn(mDevicePolicyManager);
@@ -161,13 +173,16 @@ public class TrustManagerServiceTest {
when(mPackageManager.checkPermission(any(), any())).thenReturn(
PackageManager.PERMISSION_GRANTED);
- when(mUserManager.getAliveUsers()).thenReturn(
- List.of(new UserInfo(TEST_USER_ID, "user", UserInfo.FLAG_FULL)));
+ when(mUserManager.getAliveUsers()).thenReturn(List.of(TEST_USER));
+ when(mUserManager.getEnabledProfileIds(TEST_USER_ID)).thenReturn(new int[0]);
+ when(mUserManager.getUserInfo(TEST_USER_ID)).thenReturn(TEST_USER);
when(mWindowManager.isKeyguardLocked()).thenReturn(true);
mMockContext.addMockSystemService(ActivityManager.class, mActivityManager);
mMockContext.addMockSystemService(BiometricManager.class, mBiometricManager);
+ mMockContext.addMockSystemService(FaceManager.class, mFaceManager);
+ mMockContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
mMockContext.setMockPackageManager(mPackageManager);
mMockContext.addMockSystemService(UserManager.class, mUserManager);
doReturn(mWindowManager).when(() -> WindowManagerGlobal.getWindowManagerService());
@@ -362,9 +377,9 @@ public class TrustManagerServiceTest {
when(mWindowManager.isKeyguardLocked()).thenReturn(true);
mTrustManager.reportKeyguardShowingChanged();
verify(mKeystoreAuthorization)
- .onDeviceLocked(eq(PARENT_USER_ID), eq(PARENT_BIOMETRIC_SIDS));
+ .onDeviceLocked(eq(PARENT_USER_ID), eq(PARENT_BIOMETRIC_SIDS), eq(false));
verify(mKeystoreAuthorization)
- .onDeviceLocked(eq(PROFILE_USER_ID), eq(PARENT_BIOMETRIC_SIDS));
+ .onDeviceLocked(eq(PROFILE_USER_ID), eq(PARENT_BIOMETRIC_SIDS), eq(false));
}
// Tests that when the device is locked for a managed profile with a *separate* challenge, the
@@ -381,7 +396,188 @@ public class TrustManagerServiceTest {
mTrustManager.setDeviceLockedForUser(PROFILE_USER_ID, true);
verify(mKeystoreAuthorization)
- .onDeviceLocked(eq(PROFILE_USER_ID), eq(PROFILE_BIOMETRIC_SIDS));
+ .onDeviceLocked(eq(PROFILE_USER_ID), eq(PROFILE_BIOMETRIC_SIDS), eq(false));
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockEnabled_whenWeakFingerprintIsSetupAndAllowed()
+ throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFingerprint(SensorProperties.STRENGTH_WEAK);
+ verifyWeakUnlockEnabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockEnabled_whenWeakFaceIsSetupAndAllowed() throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFace(SensorProperties.STRENGTH_WEAK);
+ verifyWeakUnlockEnabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockEnabled_whenConvenienceFingerprintIsSetupAndAllowed()
+ throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFingerprint(SensorProperties.STRENGTH_CONVENIENCE);
+ verifyWeakUnlockEnabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockEnabled_whenConvenienceFaceIsSetupAndAllowed()
+ throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFace(SensorProperties.STRENGTH_CONVENIENCE);
+ verifyWeakUnlockEnabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockDisabled_whenStrongAuthRequired() throws Exception {
+ setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, true);
+ setupFace(SensorProperties.STRENGTH_WEAK);
+ verifyWeakUnlockDisabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockDisabled_whenNonStrongBiometricNotAllowed() throws Exception {
+ setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED,
+ /* isNonStrongBiometricAllowed= */ false);
+ setupFace(SensorProperties.STRENGTH_WEAK);
+ verifyWeakUnlockDisabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockDisabled_whenWeakFingerprintSensorIsPresentButNotEnrolled()
+ throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFingerprint(SensorProperties.STRENGTH_WEAK, /* enrolled= */ false);
+ verifyWeakUnlockDisabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockDisabled_whenWeakFaceSensorIsPresentButNotEnrolled()
+ throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFace(SensorProperties.STRENGTH_WEAK, /* enrolled= */ false);
+ verifyWeakUnlockDisabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void
+ testKeystoreWeakUnlockDisabled_whenWeakFingerprintIsSetupButForbiddenByDevicePolicy()
+ throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFingerprint(SensorProperties.STRENGTH_WEAK);
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, TEST_USER_ID))
+ .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT);
+ verifyWeakUnlockDisabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockDisabled_whenWeakFaceIsSetupButForbiddenByDevicePolicy()
+ throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFace(SensorProperties.STRENGTH_WEAK);
+ when(mDevicePolicyManager.getKeyguardDisabledFeatures(null, TEST_USER_ID))
+ .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FACE);
+ verifyWeakUnlockDisabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockDisabled_whenOnlyStrongFingerprintIsSetup() throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFingerprint(SensorProperties.STRENGTH_STRONG);
+ verifyWeakUnlockDisabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockDisabled_whenOnlyStrongFaceIsSetup() throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ setupFace(SensorProperties.STRENGTH_STRONG);
+ verifyWeakUnlockDisabled();
+ }
+
+ @Test
+ @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
+ public void testKeystoreWeakUnlockDisabled_whenNoBiometricsAreSetup() throws Exception {
+ setupStrongAuthTrackerToAllowEverything();
+ verifyWeakUnlockDisabled();
+ }
+
+ private void setupStrongAuthTrackerToAllowEverything() throws Exception {
+ setupStrongAuthTracker(StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED, true);
+ }
+
+ private void setupStrongAuthTracker(int strongAuthFlags, boolean isNonStrongBiometricAllowed)
+ throws Exception {
+ bootService();
+ mService.onUserSwitching(null, new SystemService.TargetUser(TEST_USER));
+
+ ArgumentCaptor<StrongAuthTracker> strongAuthTracker =
+ ArgumentCaptor.forClass(StrongAuthTracker.class);
+ verify(mLockPatternUtils).registerStrongAuthTracker(strongAuthTracker.capture());
+ strongAuthTracker.getValue().getStub().onStrongAuthRequiredChanged(
+ strongAuthFlags, TEST_USER_ID);
+ strongAuthTracker.getValue().getStub().onIsNonStrongBiometricAllowedChanged(
+ isNonStrongBiometricAllowed, TEST_USER_ID);
+ mService.waitForIdle();
+ }
+
+ private void setupFingerprint(int strength) {
+ setupFingerprint(strength, /* enrolled= */ true);
+ }
+
+ private void setupFingerprint(int strength, boolean enrolled) {
+ int sensorId = 100;
+ List<SensorProperties.ComponentInfo> componentInfo = List.of();
+ SensorProperties sensor = new SensorProperties(sensorId, strength, componentInfo);
+ when(mFingerprintManager.getSensorProperties()).thenReturn(List.of(sensor));
+ when(mFingerprintManager.hasEnrolledTemplates(TEST_USER_ID)).thenReturn(enrolled);
+ }
+
+ private void setupFace(int strength) {
+ setupFace(strength, /* enrolled= */ true);
+ }
+
+ private void setupFace(int strength, boolean enrolled) {
+ int sensorId = 100;
+ List<SensorProperties.ComponentInfo> componentInfo = List.of();
+ FaceSensorProperties sensor = new FaceSensorProperties(
+ sensorId, strength, componentInfo, FaceSensorProperties.TYPE_RGB);
+ when(mFaceManager.getSensorProperties()).thenReturn(List.of(sensor));
+ when(mFaceManager.hasEnrolledTemplates(TEST_USER_ID)).thenReturn(enrolled);
+ }
+
+ private void verifyWeakUnlockEnabled() throws Exception {
+ verifyWeakUnlockValue(true);
+ }
+
+ private void verifyWeakUnlockDisabled() throws Exception {
+ verifyWeakUnlockValue(false);
+ }
+
+ // Simulates a device unlock and a device lock, then verifies that the expected
+ // weakUnlockEnabled flag was passed to Keystore's onDeviceLocked method.
+ private void verifyWeakUnlockValue(boolean expectedWeakUnlockEnabled) throws Exception {
+ when(mWindowManager.isKeyguardLocked()).thenReturn(false);
+ mTrustManager.reportKeyguardShowingChanged();
+ verify(mKeystoreAuthorization).onDeviceUnlocked(TEST_USER_ID, null);
+
+ when(mWindowManager.isKeyguardLocked()).thenReturn(true);
+ mTrustManager.reportKeyguardShowingChanged();
+ verify(mKeystoreAuthorization).onDeviceLocked(eq(TEST_USER_ID), any(),
+ eq(expectedWeakUnlockEnabled));
}
private void setupMocksForProfile(boolean unifiedChallenge) {
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 00450267ee79..0831086b28ca 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -29,8 +29,6 @@ android_test {
"src/**/*.java",
"src/**/*.kt",
- "test-apps/JobTestApp/src/**/*.java",
-
"test-apps/SuspendTestApp/src/**/*.java",
],
static_libs: [
@@ -124,7 +122,6 @@ android_test {
},
data: [
- ":JobTestApp",
":SimpleServiceTestApp1",
":SimpleServiceTestApp2",
":SimpleServiceTestApp3",
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index b1d50399416a..27c522d68119 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -29,7 +29,6 @@
<option name="cleanup-apks" value="true" />
<option name="install-arg" value="-t" />
<option name="test-file-name" value="FrameworksServicesTests.apk" />
- <option name="test-file-name" value="JobTestApp.apk" />
<option name="test-file-name" value="SuspendTestApp.apk" />
<option name="test-file-name" value="SimpleServiceTestApp1.apk" />
<option name="test-file-name" value="SimpleServiceTestApp2.apk" />
diff --git a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java b/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
deleted file mode 100644
index 523c5c060cf5..000000000000
--- a/services/tests/servicestests/src/com/android/server/BootReceiverTest.java
+++ /dev/null
@@ -1,97 +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.server;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.test.AndroidTestCase;
-
-import com.android.server.os.TombstoneProtos;
-import com.android.server.os.TombstoneProtos.Tombstone;
-
-public class BootReceiverTest extends AndroidTestCase {
- private static final String TAG = "BootReceiverTest";
-
- public void testRemoveMemoryFromTombstone() {
- Tombstone tombstoneBase = Tombstone.newBuilder()
- .setBuildFingerprint("build_fingerprint")
- .setRevision("revision")
- .setPid(123)
- .setTid(23)
- .setUid(34)
- .setSelinuxLabel("selinux_label")
- .addCommandLine("cmd1")
- .addCommandLine("cmd2")
- .addCommandLine("cmd3")
- .setProcessUptime(300)
- .setAbortMessage("abort")
- .addCauses(TombstoneProtos.Cause.newBuilder()
- .setHumanReadable("cause1")
- .setMemoryError(TombstoneProtos.MemoryError.newBuilder()
- .setTool(TombstoneProtos.MemoryError.Tool.SCUDO)
- .setType(TombstoneProtos.MemoryError.Type.DOUBLE_FREE)))
- .addLogBuffers(TombstoneProtos.LogBuffer.newBuilder().setName("name").addLogs(
- TombstoneProtos.LogMessage.newBuilder()
- .setTimestamp("123")
- .setMessage("message")))
- .addOpenFds(TombstoneProtos.FD.newBuilder().setFd(1).setPath("path"))
- .build();
-
- Tombstone tombstoneWithoutMemory = tombstoneBase.toBuilder()
- .putThreads(1, TombstoneProtos.Thread.newBuilder()
- .setId(1)
- .setName("thread1")
- .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1))
- .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2))
- .addBacktraceNote("backtracenote1")
- .addUnreadableElfFiles("files1")
- .setTaggedAddrCtrl(1)
- .setPacEnabledKeys(10)
- .build())
- .build();
-
- Tombstone tombstoneWithMemory = tombstoneBase.toBuilder()
- .addMemoryMappings(TombstoneProtos.MemoryMapping.newBuilder()
- .setBeginAddress(1)
- .setEndAddress(100)
- .setOffset(10)
- .setRead(true)
- .setWrite(true)
- .setExecute(false)
- .setMappingName("mapping")
- .setBuildId("build")
- .setLoadBias(70))
- .putThreads(1, TombstoneProtos.Thread.newBuilder()
- .setId(1)
- .setName("thread1")
- .addRegisters(TombstoneProtos.Register.newBuilder().setName("r1").setU64(1))
- .addRegisters(TombstoneProtos.Register.newBuilder().setName("r2").setU64(2))
- .addBacktraceNote("backtracenote1")
- .addUnreadableElfFiles("files1")
- .addMemoryDump(TombstoneProtos.MemoryDump.newBuilder()
- .setRegisterName("register1")
- .setMappingName("mapping")
- .setBeginAddress(10))
- .setTaggedAddrCtrl(1)
- .setPacEnabledKeys(10)
- .build())
- .build();
-
- assertThat(BootReceiver.removeMemoryFromTombstone(tombstoneWithMemory))
- .isEqualTo(tombstoneWithoutMemory);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
index 543fa5727f19..62073497b735 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDevicePlaybackTest.java
@@ -1775,6 +1775,11 @@ public class HdmiCecLocalDevicePlaybackTest {
@Test
public void wakeUp_hotPlugIn_invokesDeviceDiscoveryOnce() {
+ // There might be a leftover HotplugDetectionAction that can interfere with the test.
+ mHdmiCecLocalDevicePlayback.removeAction(HotplugDetectionAction.class);
+
+ long pollingDelay = TimeUnit.SECONDS.toMillis(60);
+ mHdmiCecController.setPollDevicesDelay(pollingDelay);
mNativeWrapper.setPollAddressResponse(Constants.ADDR_PLAYBACK_2, SendMessageResult.SUCCESS);
mHdmiControlService.onWakeUp(HdmiControlService.WAKE_UP_SCREEN_ON);
mTestLooper.dispatchAll();
@@ -1783,6 +1788,14 @@ public class HdmiCecLocalDevicePlaybackTest {
mTestLooper.dispatchAll();
assertThat(mHdmiCecLocalDevicePlayback.getActions(DeviceDiscoveryAction.class)).hasSize(1);
+ mTestLooper.moveTimeForward(pollingDelay);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage givePhysicalAddress = HdmiCecMessageBuilder.buildGivePhysicalAddress(
+ Constants.ADDR_PLAYBACK_1,
+ Constants.ADDR_PLAYBACK_2);
+ assertThat(mNativeWrapper.getResultMessages().stream()
+ .filter(message -> message.equals(givePhysicalAddress)).count()).isEqualTo(1);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java b/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
deleted file mode 100644
index e871fc567107..000000000000
--- a/services/tests/servicestests/src/com/android/server/job/BackgroundRestrictionsTest.java
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.job;
-
-import static com.android.servicestests.apps.jobtestapp.TestJobService.ACTION_JOB_STARTED;
-import static com.android.servicestests.apps.jobtestapp.TestJobService.ACTION_JOB_STOPPED;
-import static com.android.servicestests.apps.jobtestapp.TestJobService.JOB_PARAMS_EXTRA_KEY;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.app.ActivityManager;
-import android.app.AppOpsManager;
-import android.app.IActivityManager;
-import android.app.job.JobParameters;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.IDeviceIdleController;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
-import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.servicestests.apps.jobtestapp.TestJobActivity;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests that background restrictions on jobs work as expected.
- * This test requires test-apps/JobTestApp to be installed on the device.
- * To run this test from root of checkout:
- * <pre>
- * mmm -j32 frameworks/base/services/tests/servicestests/
- * adb install -r $OUT/data/app/JobTestApp/JobTestApp.apk
- * adb install -r $OUT/data/app/FrameworksServicesTests/FrameworksServicesTests.apk
- * adb shell am instrument -e class 'com.android.server.job.BackgroundRestrictionsTest' -w \
- * com.android.frameworks.servicestests
- * </pre>
- */
-@RunWith(AndroidJUnit4.class)
-@LargeTest
-public class BackgroundRestrictionsTest {
- private static final String TAG = BackgroundRestrictionsTest.class.getSimpleName();
- private static final String TEST_APP_PACKAGE = "com.android.servicestests.apps.jobtestapp";
- private static final String TEST_APP_ACTIVITY = TEST_APP_PACKAGE + ".TestJobActivity";
- private static final long POLL_INTERVAL = 500;
- private static final long DEFAULT_WAIT_TIMEOUT = 10_000;
-
- private Context mContext;
- private AppOpsManager mAppOpsManager;
- private IDeviceIdleController mDeviceIdleController;
- private IActivityManager mIActivityManager;
- private volatile int mTestJobId = -1;
- private int mTestPackageUid;
- /* accesses must be synchronized on itself */
- private final TestJobStatus mTestJobStatus = new TestJobStatus();
- private final BroadcastReceiver mJobStateChangeReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- final JobParameters params = intent.getParcelableExtra(JOB_PARAMS_EXTRA_KEY);
- Log.d(TAG, "Received action " + intent.getAction());
- synchronized (mTestJobStatus) {
- switch (intent.getAction()) {
- case ACTION_JOB_STARTED:
- mTestJobStatus.running = true;
- mTestJobStatus.jobId = params.getJobId();
- mTestJobStatus.stopReason = JobParameters.STOP_REASON_UNDEFINED;
- break;
- case ACTION_JOB_STOPPED:
- mTestJobStatus.running = false;
- mTestJobStatus.jobId = params.getJobId();
- mTestJobStatus.stopReason = params.getStopReason();
- break;
- }
- }
- }
- };
-
- @Before
- public void setUp() throws Exception {
- mContext = InstrumentationRegistry.getTargetContext();
- mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
- mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
- ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
- mIActivityManager = ActivityManager.getService();
- mTestPackageUid = mContext.getPackageManager().getPackageUid(TEST_APP_PACKAGE, 0);
- mTestJobStatus.reset();
- final IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(ACTION_JOB_STARTED);
- intentFilter.addAction(ACTION_JOB_STOPPED);
- mContext.registerReceiver(mJobStateChangeReceiver, intentFilter,
- Context.RECEIVER_EXPORTED_UNAUDITED);
- setAppOpsModeAllowed(true);
- setPowerExemption(false);
- }
-
- private void scheduleTestJob() {
- mTestJobId = (int) (SystemClock.uptimeMillis() / 1000);
- final Intent scheduleJobIntent = new Intent(TestJobActivity.ACTION_START_JOB);
- scheduleJobIntent.putExtra(TestJobActivity.EXTRA_JOB_ID_KEY, mTestJobId);
- scheduleJobIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- scheduleJobIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY));
- mContext.startActivity(scheduleJobIntent);
- }
-
- private void scheduleAndAssertJobStarted() throws Exception {
- scheduleTestJob();
- Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY);
- assertTrue("Job did not start after scheduling", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
- }
-
- @FlakyTest
- @Test
- public void testPowerExemption() throws Exception {
- scheduleAndAssertJobStarted();
- setAppOpsModeAllowed(false);
- mIActivityManager.makePackageIdle(TEST_APP_PACKAGE, UserHandle.USER_CURRENT);
- assertTrue("Job did not stop after putting app under bg-restriction",
- awaitJobStop(DEFAULT_WAIT_TIMEOUT,
- JobParameters.STOP_REASON_BACKGROUND_RESTRICTION));
-
- setPowerExemption(true);
- scheduleTestJob();
- Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY);
- assertTrue("Job did not start when the app was in the power exemption list",
- awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-
- setPowerExemption(false);
- assertTrue("Job did not stop after removing from the power exemption list",
- awaitJobStop(DEFAULT_WAIT_TIMEOUT,
- JobParameters.STOP_REASON_BACKGROUND_RESTRICTION));
-
- scheduleTestJob();
- Thread.sleep(TestJobActivity.JOB_MINIMUM_LATENCY);
- assertFalse("Job started under bg-restrictions", awaitJobStart(DEFAULT_WAIT_TIMEOUT));
- setPowerExemption(true);
- assertTrue("Job did not start when the app was in the power exemption list",
- awaitJobStart(DEFAULT_WAIT_TIMEOUT));
- }
-
- @After
- public void tearDown() throws Exception {
- final Intent cancelJobsIntent = new Intent(TestJobActivity.ACTION_CANCEL_JOBS);
- cancelJobsIntent.setComponent(new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY));
- cancelJobsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mContext.startActivity(cancelJobsIntent);
- mContext.unregisterReceiver(mJobStateChangeReceiver);
- Thread.sleep(500); // To avoid race with register in the next setUp
- setAppOpsModeAllowed(true);
- setPowerExemption(false);
- }
-
- private void setPowerExemption(boolean exempt) throws RemoteException {
- if (exempt) {
- mDeviceIdleController.addPowerSaveWhitelistApp(TEST_APP_PACKAGE);
- } else {
- mDeviceIdleController.removePowerSaveWhitelistApp(TEST_APP_PACKAGE);
- }
- }
-
- private void setAppOpsModeAllowed(boolean allow) {
- mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mTestPackageUid,
- TEST_APP_PACKAGE, allow ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED);
- }
-
- private boolean awaitJobStart(long timeout) throws InterruptedException {
- return waitUntilTrue(timeout, () -> {
- synchronized (mTestJobStatus) {
- return (mTestJobStatus.jobId == mTestJobId) && mTestJobStatus.running;
- }
- });
- }
-
- private boolean awaitJobStop(long timeout, @JobParameters.StopReason int expectedStopReason)
- throws InterruptedException {
- return waitUntilTrue(timeout, () -> {
- synchronized (mTestJobStatus) {
- return (mTestJobStatus.jobId == mTestJobId) && !mTestJobStatus.running
- && (expectedStopReason == JobParameters.STOP_REASON_UNDEFINED
- || mTestJobStatus.stopReason == expectedStopReason);
- }
- });
- }
-
- private boolean waitUntilTrue(long timeout, Condition condition) throws InterruptedException {
- final long deadLine = SystemClock.uptimeMillis() + timeout;
- do {
- Thread.sleep(POLL_INTERVAL);
- } while (!condition.isTrue() && SystemClock.uptimeMillis() < deadLine);
- return condition.isTrue();
- }
-
- private static final class TestJobStatus {
- int jobId;
- int stopReason;
- boolean running;
-
- private void reset() {
- running = false;
- stopReason = jobId = 0;
- }
- }
-
- private interface Condition {
- boolean isTrue();
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 0f5fb91a140f..d50affba1ea1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -406,8 +406,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
public void testPushDynamicShortcut() {
// Change the max number of shortcuts.
- mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5");
-
+ mService.updateConfigurationLocked(ConfigConstants.KEY_MAX_SHORTCUTS + "=5,"
+ + ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=1");
setCaller(CALLING_PACKAGE_1, USER_0);
final ShortcutInfo s1 = makeShortcut("s1");
@@ -545,6 +545,57 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
eq(CALLING_PACKAGE_1), eq("s9"), eq(USER_0));
}
+ public void testPushDynamicShortcut_CallsToUsageStatsManagerAreThrottled()
+ throws InterruptedException {
+ mService.updateConfigurationLocked(
+ ShortcutService.ConfigConstants.KEY_SAVE_DELAY_MILLIS + "=500");
+
+ // Verify calls to UsageStatsManagerInternal#reportShortcutUsage are throttled.
+ setCaller(CALLING_PACKAGE_1, USER_0);
+ {
+ final ShortcutInfo si = makeShortcut("s0");
+ mManager.pushDynamicShortcut(si);
+ }
+ verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+ eq(CALLING_PACKAGE_1), eq("s0"), eq(USER_0));
+ Mockito.reset(mMockUsageStatsManagerInternal);
+ for (int i = 2; i <= 10; i++) {
+ final ShortcutInfo si = makeShortcut("s" + i);
+ mManager.pushDynamicShortcut(si);
+ }
+ verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
+ any(), any(), anyInt());
+
+ // Verify pkg2 isn't blocked by pkg1, but consecutive calls from pkg2 are throttled as well.
+ setCaller(CALLING_PACKAGE_2, USER_0);
+ {
+ final ShortcutInfo si = makeShortcut("s1");
+ mManager.pushDynamicShortcut(si);
+ }
+ verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+ eq(CALLING_PACKAGE_2), eq("s1"), eq(USER_0));
+ Mockito.reset(mMockUsageStatsManagerInternal);
+ for (int i = 2; i <= 10; i++) {
+ final ShortcutInfo si = makeShortcut("s" + i);
+ mManager.pushDynamicShortcut(si);
+ }
+ verify(mMockUsageStatsManagerInternal, times(0)).reportShortcutUsage(
+ any(), any(), anyInt());
+
+ Mockito.reset(mMockUsageStatsManagerInternal);
+ // Let time passes which resets the throttle
+ Thread.sleep(505);
+ // Verify UsageStatsManagerInternal#reportShortcutUsed can be called again
+ setCaller(CALLING_PACKAGE_1, USER_0);
+ mManager.pushDynamicShortcut(makeShortcut("s10"));
+ setCaller(CALLING_PACKAGE_2, USER_0);
+ mManager.pushDynamicShortcut(makeShortcut("s10"));
+ verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+ eq(CALLING_PACKAGE_1), any(), eq(USER_0));
+ verify(mMockUsageStatsManagerInternal, times(1)).reportShortcutUsage(
+ eq(CALLING_PACKAGE_2), any(), eq(USER_0));
+ }
+
public void testUnlimitedCalls() {
setCaller(CALLING_PACKAGE_1, USER_0);
diff --git a/services/tests/servicestests/test-apps/JobTestApp/Android.bp b/services/tests/servicestests/test-apps/JobTestApp/Android.bp
deleted file mode 100644
index 6458bcd4fc8f..000000000000
--- a/services/tests/servicestests/test-apps/JobTestApp/Android.bp
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_base_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_base_license"],
-}
-
-android_test_helper_app {
- name: "JobTestApp",
-
- sdk_version: "current",
-
- srcs: ["**/*.java"],
-
- dex_preopt: {
- enabled: false,
- },
- optimize: {
- enabled: false,
- },
-}
diff --git a/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml
deleted file mode 100644
index ac35805e8af6..000000000000
--- a/services/tests/servicestests/test-apps/JobTestApp/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.servicestests.apps.jobtestapp">
-
- <application>
- <service android:name=".TestJobService"
- android:permission="android.permission.BIND_JOB_SERVICE" />
- <activity android:name=".TestJobActivity"
- android:exported="true" />
- </application>
-
-</manifest> \ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/JobTestApp/OWNERS b/services/tests/servicestests/test-apps/JobTestApp/OWNERS
deleted file mode 100644
index 6f207fb1a00e..000000000000
--- a/services/tests/servicestests/test-apps/JobTestApp/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /apex/jobscheduler/OWNERS
diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
deleted file mode 100644
index 99eb196e3298..000000000000
--- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.servicestests.apps.jobtestapp;
-
-import android.app.Activity;
-import android.app.job.JobInfo;
-import android.app.job.JobScheduler;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-
-public class TestJobActivity extends Activity {
- private static final String TAG = TestJobActivity.class.getSimpleName();
- private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp";
-
- public static final String EXTRA_JOB_ID_KEY = PACKAGE_NAME + ".extra.JOB_ID";
- public static final String ACTION_START_JOB = PACKAGE_NAME + ".action.START_JOB";
- public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS";
- public static final int JOB_INITIAL_BACKOFF = 10_000;
- public static final int JOB_MINIMUM_LATENCY = 5_000;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- ComponentName jobServiceComponent = new ComponentName(this, TestJobService.class);
- JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
- final Intent intent = getIntent();
- switch (intent.getAction()) {
- case ACTION_CANCEL_JOBS:
- jobScheduler.cancelAll();
- Log.d(TAG, "Cancelled all jobs for " + getPackageName());
- break;
- case ACTION_START_JOB:
- final int jobId = intent.getIntExtra(EXTRA_JOB_ID_KEY, hashCode());
- JobInfo.Builder jobBuilder = new JobInfo.Builder(jobId, jobServiceComponent)
- .setBackoffCriteria(JOB_INITIAL_BACKOFF, JobInfo.BACKOFF_POLICY_LINEAR)
- .setMinimumLatency(JOB_MINIMUM_LATENCY)
- .setOverrideDeadline(JOB_MINIMUM_LATENCY);
- final int result = jobScheduler.schedule(jobBuilder.build());
- if (result != JobScheduler.RESULT_SUCCESS) {
- Log.e(TAG, "Could not schedule job " + jobId);
- } else {
- Log.d(TAG, "Successfully scheduled job with id " + jobId);
- }
- break;
- default:
- Log.e(TAG, "Unknown action " + intent.getAction());
- }
- finish();
- }
-} \ No newline at end of file
diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
deleted file mode 100644
index b8585f26c185..000000000000
--- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobService.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.servicestests.apps.jobtestapp;
-
-import android.annotation.TargetApi;
-import android.app.job.JobParameters;
-import android.app.job.JobService;
-import android.content.Intent;
-import android.util.Log;
-
-@TargetApi(24)
-public class TestJobService extends JobService {
- private static final String TAG = TestJobService.class.getSimpleName();
- private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp";
- public static final String ACTION_JOB_STARTED = PACKAGE_NAME + ".action.JOB_STARTED";
- public static final String ACTION_JOB_STOPPED = PACKAGE_NAME + ".action.JOB_STOPPED";
- public static final String JOB_PARAMS_EXTRA_KEY = PACKAGE_NAME + ".extra.JOB_PARAMETERS";
-
- @Override
- public boolean onStartJob(JobParameters params) {
- Log.i(TAG, "Test job executing: " + params.getJobId());
- Intent reportJobStartIntent = new Intent(ACTION_JOB_STARTED);
- reportJobStartIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- sendBroadcast(reportJobStartIntent);
- return true;
- }
-
- @Override
- public boolean onStopJob(JobParameters params) {
- Log.i(TAG, "Test job stopped executing: " + params.getJobId());
- Intent reportJobStopIntent = new Intent(ACTION_JOB_STOPPED);
- reportJobStopIntent.putExtra(JOB_PARAMS_EXTRA_KEY, params)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
- sendBroadcast(reportJobStopIntent);
- // Deadline constraint is dropped on reschedule, so it's more reliable to use a new job.
- return false;
- }
-}
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 4e1c72af2727..2f29d10ec2f9 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -47,6 +47,7 @@ android_test {
"flag-junit",
"notification_flags_lib",
"platform-test-rules",
+ "SettingsLib",
],
libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
index db4653208f62..839cf7ce4926 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java
@@ -17,6 +17,9 @@
package com.android.server;
import static android.Manifest.permission.MODIFY_DAY_NIGHT_MODE;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_DAY;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
import static android.app.UiModeManager.MODE_NIGHT_AUTO;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
@@ -32,6 +35,7 @@ import static com.android.server.UiModeManagerService.SUPPORTED_NIGHT_MODE_CUSTO
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.fail;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
@@ -65,6 +69,7 @@ import static org.testng.Assert.assertThrows;
import android.Manifest;
import android.app.Activity;
import android.app.AlarmManager;
+import android.app.Flags;
import android.app.IOnProjectionStateChangedListener;
import android.app.IUiModeManager;
import android.content.BroadcastReceiver;
@@ -84,6 +89,8 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.test.FakePermissionEnforcer;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.service.dreams.DreamManagerInternal;
import android.test.mock.MockContentResolver;
@@ -98,6 +105,7 @@ import com.android.server.wm.WindowManagerInternal;
import org.junit.Before;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -109,6 +117,7 @@ import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.List;
+import java.util.Map;
import java.util.function.Consumer;
@RunWith(AndroidTestingRunner.class)
@@ -159,6 +168,11 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
private TwilightListener mTwilightListener;
private FakePermissionEnforcer mPermissionEnforcer;
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(
+ SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT);
+
+
@Before
public void setUp() {
// The AIDL stub will use PermissionEnforcer to check permission from the caller.
@@ -1437,6 +1451,51 @@ public class UiModeManagerServiceTest extends UiServiceTestCase {
verify(mInjector).startDreamWhenDockedIfAppropriate(mContext);
}
+ private void testAttentionModeThemeOverlay(boolean modeNight) throws RemoteException {
+ //setup
+ if (modeNight) {
+ mService.setNightMode(MODE_NIGHT_YES);
+ assertTrue(mUiManagerService.getConfiguration().isNightModeActive());
+ } else {
+ mService.setNightMode(MODE_NIGHT_NO);
+ assertFalse(mUiManagerService.getConfiguration().isNightModeActive());
+ }
+
+ // attention modes with expected night modes
+ Map<Integer, Boolean> modes = Map.of(
+ MODE_ATTENTION_THEME_OVERLAY_OFF, modeNight,
+ MODE_ATTENTION_THEME_OVERLAY_DAY, false,
+ MODE_ATTENTION_THEME_OVERLAY_NIGHT, true
+ );
+
+ // test
+ for (int aMode : modes.keySet()) {
+ try {
+ mService.setAttentionModeThemeOverlay(aMode);
+
+ int appliedAMode = mService.getAttentionModeThemeOverlay();
+ boolean nMode = modes.get(aMode);
+
+ assertEquals(aMode, appliedAMode);
+ assertEquals(isNightModeActivated(), nMode);
+ } catch (RemoteException e) {
+ fail("Error communicating with server: " + e.getMessage());
+ }
+ }
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testAttentionModeThemeOverlay_nightModeDisabled() throws RemoteException {
+ testAttentionModeThemeOverlay(false);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_MODES_API)
+ public void testAttentionModeThemeOverlay_nightModeEnabled() throws RemoteException {
+ testAttentionModeThemeOverlay(true);
+ }
+
private void triggerDockIntent() {
final Intent dockedIntent =
new Intent(Intent.ACTION_DOCK_EVENT)
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
index 30843d222742..3797dbb97213 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java
@@ -16,7 +16,8 @@
package com.android.server.notification;
-import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_NIGHT;
+import static android.app.UiModeManager.MODE_ATTENTION_THEME_OVERLAY_OFF;
import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP;
import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER;
@@ -121,28 +122,49 @@ public class DefaultDeviceEffectsApplierTest {
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
verify(mColorDisplayManager).setSaturationLevel(eq(0));
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
- verify(mUiModeManager).setNightModeActivatedForCustomMode(
- eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+ verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
}
@Test
- public void apply_removesPreviouslyAppliedEffects() {
+ public void apply_removesEffects() {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder()
.setShouldSuppressAmbientDisplay(true)
.setShouldDimWallpaper(true)
+ .setShouldDisplayGrayscale(true)
+ .setShouldUseNightMode(true)
.build();
mApplier.apply(previousEffects, UPDATE_ORIGIN_USER);
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
+ verify(mColorDisplayManager).setSaturationLevel(eq(0));
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
+ verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build();
mApplier.apply(noEffects, UPDATE_ORIGIN_USER);
verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
+ verify(mColorDisplayManager).setSaturationLevel(eq(100));
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f));
- verifyZeroInteractions(mColorDisplayManager, mUiModeManager);
+ verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_OFF));
+ }
+
+ @Test
+ public void apply_removesOnlyPreviouslyAppliedEffects() {
+ mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
+
+ ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder()
+ .setShouldSuppressAmbientDisplay(true)
+ .build();
+ mApplier.apply(previousEffects, UPDATE_ORIGIN_USER);
+ verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true));
+
+ ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build();
+ mApplier.apply(noEffects, UPDATE_ORIGIN_USER);
+
+ verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false));
+ verifyZeroInteractions(mColorDisplayManager, mWallpaperManager, mUiModeManager);
}
@Test
@@ -150,6 +172,7 @@ public class DefaultDeviceEffectsApplierTest {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mContext.addMockSystemService(ColorDisplayManager.class, null);
mContext.addMockSystemService(WallpaperManager.class, null);
+ mApplier = new DefaultDeviceEffectsApplier(mContext);
ZenDeviceEffects effects = new ZenDeviceEffects.Builder()
.setShouldSuppressAmbientDisplay(true)
@@ -177,7 +200,7 @@ public class DefaultDeviceEffectsApplierTest {
verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f));
verify(mPowerManager, never()).suppressAmbientDisplay(anyString(), anyBoolean());
- verify(mUiModeManager, never()).setNightModeActivatedForCustomMode(anyInt(), anyBoolean());
+ verify(mUiModeManager, never()).setAttentionModeThemeOverlay(anyInt());
}
@Test
@@ -223,8 +246,7 @@ public class DefaultDeviceEffectsApplierTest {
screenOffReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF));
// So the effect is applied, and we stopped listening for this event.
- verify(mUiModeManager).setNightModeActivatedForCustomMode(
- eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+ verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
verify(mContext).unregisterReceiver(eq(screenOffReceiver));
}
@@ -239,8 +261,7 @@ public class DefaultDeviceEffectsApplierTest {
origin.value());
// Effect was applied, and no broadcast receiver was registered.
- verify(mUiModeManager).setNightModeActivatedForCustomMode(
- eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+ verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
verify(mContext, never()).registerReceiver(any(), any(), anyInt());
}
@@ -256,8 +277,7 @@ public class DefaultDeviceEffectsApplierTest {
origin.value());
// Effect was applied, and no broadcast receiver was registered.
- verify(mUiModeManager).setNightModeActivatedForCustomMode(
- eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true));
+ verify(mUiModeManager).setAttentionModeThemeOverlay(eq(MODE_ATTENTION_THEME_OVERLAY_NIGHT));
verify(mContext, never()).registerReceiver(any(), any(), anyInt());
}
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 c1f35ccb69e0..723ac15fb50f 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -13792,8 +13792,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
mBinderService.setNotificationPolicy("package", policy, false);
- verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy),
- eq(ZenModeConfig.UPDATE_ORIGIN_APP));
+ verify(zenHelper).applyGlobalPolicyAsImplicitZenRule(eq("package"), anyInt(), eq(policy));
}
@Test
@@ -13859,7 +13858,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
} else {
verify(zenModeHelper).applyGlobalPolicyAsImplicitZenRule(anyString(), anyInt(),
- eq(policy), anyInt());
+ eq(policy));
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
index 3d8ec2ec9277..f604f1e77cf4 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenDeviceEffectsTest.java
@@ -52,7 +52,6 @@ public class ZenDeviceEffectsTest extends UiServiceTestCase {
.setShouldMaximizeDoze(true)
.setShouldUseNightMode(false)
.setShouldSuppressAmbientDisplay(false).setShouldSuppressAmbientDisplay(true)
- .setUserModifiedFields(8)
.build();
assertThat(deviceEffects.shouldDimWallpaper()).isTrue();
@@ -65,7 +64,6 @@ public class ZenDeviceEffectsTest extends UiServiceTestCase {
assertThat(deviceEffects.shouldMinimizeRadioUsage()).isFalse();
assertThat(deviceEffects.shouldUseNightMode()).isFalse();
assertThat(deviceEffects.shouldSuppressAmbientDisplay()).isTrue();
- assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(8);
}
@Test
@@ -97,7 +95,6 @@ public class ZenDeviceEffectsTest extends UiServiceTestCase {
.setShouldMinimizeRadioUsage(true)
.setShouldUseNightMode(true)
.setShouldSuppressAmbientDisplay(true)
- .setUserModifiedFields(6)
.build();
Parcel parcel = Parcel.obtain();
@@ -116,7 +113,6 @@ public class ZenDeviceEffectsTest extends UiServiceTestCase {
assertThat(copy.shouldUseNightMode()).isTrue();
assertThat(copy.shouldSuppressAmbientDisplay()).isTrue();
assertThat(copy.shouldDisplayGrayscale()).isFalse();
- assertThat(copy.getUserModifiedFields()).isEqualTo(6);
}
@Test
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 db92719ecfd6..e523e79f6370 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeConfigTest.java
@@ -285,7 +285,6 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertEquals(expected.getPriorityCallSenders(), actual.getPriorityCallSenders());
assertEquals(expected.getPriorityMessageSenders(), actual.getPriorityMessageSenders());
assertEquals(expected.getPriorityChannels(), actual.getPriorityChannels());
- assertEquals(expected.getUserModifiedFields(), actual.getUserModifiedFields());
}
@Test
@@ -342,45 +341,32 @@ public class ZenModeConfigTest extends UiServiceTestCase {
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
rule.zenPolicy = null;
rule.zenDeviceEffects = null;
-
assertThat(rule.canBeUpdatedByApp()).isTrue();
rule.userModifiedFields = 1;
+
assertThat(rule.canBeUpdatedByApp()).isFalse();
}
@Test
public void testCanBeUpdatedByApp_policyModified() throws Exception {
- ZenPolicy.Builder policyBuilder = new ZenPolicy.Builder().setUserModifiedFields(0);
- ZenPolicy policy = policyBuilder.build();
-
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
- rule.zenPolicy = policy;
-
- assertThat(rule.userModifiedFields).isEqualTo(0);
+ rule.zenPolicy = new ZenPolicy();
assertThat(rule.canBeUpdatedByApp()).isTrue();
- policy = policyBuilder.setUserModifiedFields(1).build();
- assertThat(policy.getUserModifiedFields()).isEqualTo(1);
- rule.zenPolicy = policy;
+ rule.zenPolicyUserModifiedFields = 1;
+
assertThat(rule.canBeUpdatedByApp()).isFalse();
}
@Test
public void testCanBeUpdatedByApp_deviceEffectsModified() throws Exception {
- ZenDeviceEffects.Builder deviceEffectsBuilder =
- new ZenDeviceEffects.Builder().setUserModifiedFields(0);
- ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build();
-
ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
- rule.zenDeviceEffects = deviceEffects;
-
- assertThat(rule.userModifiedFields).isEqualTo(0);
+ rule.zenDeviceEffects = new ZenDeviceEffects.Builder().build();
assertThat(rule.canBeUpdatedByApp()).isTrue();
- deviceEffects = deviceEffectsBuilder.setUserModifiedFields(1).build();
- assertThat(deviceEffects.getUserModifiedFields()).isEqualTo(1);
- rule.zenDeviceEffects = deviceEffects;
+ rule.zenDeviceEffectsUserModifiedFields = 1;
+
assertThat(rule.canBeUpdatedByApp()).isFalse();
}
@@ -406,6 +392,8 @@ public class ZenModeConfigTest extends UiServiceTestCase {
rule.allowManualInvocation = ALLOW_MANUAL;
rule.type = TYPE;
rule.userModifiedFields = 16;
+ rule.zenPolicyUserModifiedFields = 5;
+ rule.zenDeviceEffectsUserModifiedFields = 2;
rule.iconResName = ICON_RES_NAME;
rule.triggerDescription = TRIGGER_DESC;
rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);
@@ -432,6 +420,9 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertEquals(rule.iconResName, parceled.iconResName);
assertEquals(rule.type, parceled.type);
assertEquals(rule.userModifiedFields, parceled.userModifiedFields);
+ assertEquals(rule.zenPolicyUserModifiedFields, parceled.zenPolicyUserModifiedFields);
+ assertEquals(rule.zenDeviceEffectsUserModifiedFields,
+ parceled.zenDeviceEffectsUserModifiedFields);
assertEquals(rule.triggerDescription, parceled.triggerDescription);
assertEquals(rule.zenPolicy, parceled.zenPolicy);
assertEquals(rule.deletionInstant, parceled.deletionInstant);
@@ -511,6 +502,8 @@ public class ZenModeConfigTest extends UiServiceTestCase {
rule.allowManualInvocation = ALLOW_MANUAL;
rule.type = TYPE;
rule.userModifiedFields = 4;
+ rule.zenPolicyUserModifiedFields = 5;
+ rule.zenDeviceEffectsUserModifiedFields = 2;
rule.iconResName = ICON_RES_NAME;
rule.triggerDescription = TRIGGER_DESC;
rule.deletionInstant = Instant.ofEpochMilli(1701790147000L);
@@ -541,6 +534,9 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertEquals(rule.allowManualInvocation, fromXml.allowManualInvocation);
assertEquals(rule.type, fromXml.type);
assertEquals(rule.userModifiedFields, fromXml.userModifiedFields);
+ assertEquals(rule.zenPolicyUserModifiedFields, fromXml.zenPolicyUserModifiedFields);
+ assertEquals(rule.zenDeviceEffectsUserModifiedFields,
+ fromXml.zenDeviceEffectsUserModifiedFields);
assertEquals(rule.triggerDescription, fromXml.triggerDescription);
assertEquals(rule.iconResName, fromXml.iconResName);
assertEquals(rule.deletionInstant, fromXml.deletionInstant);
@@ -697,7 +693,6 @@ public class ZenModeConfigTest extends UiServiceTestCase {
.allowPriorityChannels(false)
.hideAllVisualEffects()
.showVisualEffect(ZenPolicy.VISUAL_EFFECT_AMBIENT, true)
- .setUserModifiedFields(4)
.build();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -732,7 +727,6 @@ public class ZenModeConfigTest extends UiServiceTestCase {
assertEquals(policy.getVisualEffectAmbient(), fromXml.getVisualEffectAmbient());
assertEquals(policy.getVisualEffectNotificationList(),
fromXml.getVisualEffectNotificationList());
- assertEquals(policy.getUserModifiedFields(), fromXml.getUserModifiedFields());
}
private ZenModeConfig getMutedRingerConfig() {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
index 9d7cf53e62db..2e64645ecade 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java
@@ -73,13 +73,15 @@ public class ZenModeDiffTest extends UiServiceTestCase {
: Set.of("version", "manualRule", "automaticRules");
// Differences for flagged fields are only generated if the flag is enabled.
- // "Metadata" fields (userModifiedFields, deletionInstant) are not compared.
+ // "Metadata" fields (userModifiedFields & co, deletionInstant) are not compared.
private static final Set<String> ZEN_RULE_EXEMPT_FIELDS =
android.app.Flags.modesApi()
- ? Set.of("userModifiedFields", "deletionInstant")
+ ? Set.of("userModifiedFields", "zenPolicyUserModifiedFields",
+ "zenDeviceEffectsUserModifiedFields", "deletionInstant")
: Set.of(RuleDiff.FIELD_TYPE, RuleDiff.FIELD_TRIGGER_DESCRIPTION,
RuleDiff.FIELD_ICON_RES, RuleDiff.FIELD_ALLOW_MANUAL,
RuleDiff.FIELD_ZEN_DEVICE_EFFECTS, "userModifiedFields",
+ "zenPolicyUserModifiedFields", "zenDeviceEffectsUserModifiedFields",
"deletionInstant");
// allowPriorityChannels is flagged by android.app.modes_api
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 78fe41f31849..edc876aab388 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -46,6 +46,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+import static android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
import static android.service.notification.Condition.SOURCE_SCHEDULE;
import static android.service.notification.Condition.SOURCE_USER_ACTION;
@@ -67,6 +68,7 @@ import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW;
import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW;
import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE;
+import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.assertEquals;
@@ -295,6 +297,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
when(appInfoSpy.loadLabel(any())).thenReturn(CUSTOM_APP_LABEL);
when(mPackageManager.getApplicationInfo(eq(CUSTOM_PKG_NAME), anyInt()))
.thenReturn(appInfoSpy);
+ when(mPackageManager.getApplicationInfo(eq(mContext.getPackageName()), anyInt()))
+ .thenReturn(appInfoSpy);
mZenModeHelper.mPm = mPackageManager;
mZenModeEventLogger.reset();
@@ -2236,12 +2240,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
- // savedRule.getDeviceEffects() is equal to zde, except for the userModifiedFields.
- // So we clear before comparing.
- ZenDeviceEffects savedEffects = new ZenDeviceEffects.Builder(savedRule.getDeviceEffects())
- .setUserModifiedFields(0).build();
-
- assertThat(savedEffects).isEqualTo(zde);
+ assertThat(savedRule.getDeviceEffects()).isEqualTo(zde);
}
@Test
@@ -2331,12 +2330,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
- // savedRule.getDeviceEffects() is equal to updateFromUser, except for the
- // userModifiedFields, so we clear before comparing.
- ZenDeviceEffects savedEffects = new ZenDeviceEffects.Builder(savedRule.getDeviceEffects())
- .setUserModifiedFields(0).build();
-
- assertThat(savedEffects).isEqualTo(updateFromUser);
+ assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromUser);
}
@Test
@@ -3411,7 +3405,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
rule.allowManualInvocation = ALLOW_MANUAL;
rule.type = TYPE;
- rule.userModifiedFields = AutomaticZenRule.FIELD_NAME;
rule.iconResName = ICON_RES_NAME;
rule.triggerDescription = TRIGGER_DESC;
@@ -3426,7 +3419,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertEquals(POLICY, actual.getZenPolicy());
assertEquals(CONFIG_ACTIVITY, actual.getConfigurationActivity());
assertEquals(TYPE, actual.getType());
- assertEquals(AutomaticZenRule.FIELD_NAME, actual.getUserModifiedFields());
assertEquals(ALLOW_MANUAL, actual.isManualInvocationAllowed());
assertEquals(CREATION_TIME, actual.getCreationTime());
assertEquals(OWNER.getPackageName(), actual.getPackageName());
@@ -3453,29 +3445,31 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setManualInvocationAllowed(ALLOW_MANUAL)
.build();
- ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule();
-
- mZenModeHelper.populateZenRule(OWNER.getPackageName(), azr, rule, UPDATE_ORIGIN_APP, true);
-
- assertEquals(NAME, rule.name);
- assertEquals(OWNER, rule.component);
- assertEquals(CONDITION_ID, rule.conditionId);
- assertEquals(INTERRUPTION_FILTER_ZR, rule.zenMode);
- assertEquals(ENABLED, rule.enabled);
- assertEquals(POLICY, rule.zenPolicy);
- assertEquals(CONFIG_ACTIVITY, rule.configurationActivity);
- assertEquals(TYPE, rule.type);
- assertEquals(ALLOW_MANUAL, rule.allowManualInvocation);
- assertEquals(OWNER.getPackageName(), rule.getPkg());
- assertEquals(ICON_RES_NAME, rule.iconResName);
+ String ruleId = mZenModeHelper.addAutomaticZenRule(OWNER.getPackageName(), azr,
+ UPDATE_ORIGIN_APP, "add", CUSTOM_PKG_UID);
+
+ ZenModeConfig.ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+
+ assertThat(storedRule).isNotNull();
+ assertEquals(NAME, storedRule.name);
+ assertEquals(OWNER, storedRule.component);
+ assertEquals(CONDITION_ID, storedRule.conditionId);
+ assertEquals(INTERRUPTION_FILTER_ZR, storedRule.zenMode);
+ assertEquals(ENABLED, storedRule.enabled);
+ assertEquals(POLICY, storedRule.zenPolicy);
+ assertEquals(CONFIG_ACTIVITY, storedRule.configurationActivity);
+ assertEquals(TYPE, storedRule.type);
+ assertEquals(ALLOW_MANUAL, storedRule.allowManualInvocation);
+ assertEquals(OWNER.getPackageName(), storedRule.getPkg());
+ assertEquals(ICON_RES_NAME, storedRule.iconResName);
// Because the origin of the update is the app, we don't expect the bitmask to change.
- assertEquals(0, rule.userModifiedFields);
- assertEquals(TRIGGER_DESC, rule.triggerDescription);
+ assertEquals(0, storedRule.userModifiedFields);
+ assertEquals(TRIGGER_DESC, storedRule.triggerDescription);
}
@Test
@EnableFlags(Flags.FLAG_MODES_API)
- public void automaticZenRuleToZenRule_updatesNameUnlessUserModified() {
+ public void updateAutomaticZenRule_fromApp_updatesNameUnlessUserModified() {
// Add a starting rule with the name OriginalName.
AutomaticZenRule azrBase = new AutomaticZenRule.Builder("OriginalName", CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
@@ -3492,7 +3486,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
Process.SYSTEM_UID);
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(rule.getName()).isEqualTo("NewName");
- assertThat(rule.canUpdate()).isTrue();
// The user modifies some other field in the rule, which makes the rule as a whole not
// app modifiable.
@@ -3501,10 +3494,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.build();
mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_USER, "reason",
Process.SYSTEM_UID);
- rule = mZenModeHelper.getAutomaticZenRule(ruleId);
- assertThat(rule.getUserModifiedFields())
- .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
- assertThat(rule.canUpdate()).isFalse();
// ...but the app can still modify the name, because the name itself hasn't been modified
// by the user.
@@ -3524,8 +3513,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
Process.SYSTEM_UID);
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
assertThat(rule.getName()).isEqualTo("UserProvidedName");
- assertThat(rule.getUserModifiedFields()).isEqualTo(AutomaticZenRule.FIELD_NAME
- | AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
// The app is no longer able to modify the name.
azrUpdate = new AutomaticZenRule.Builder(rule)
@@ -3539,7 +3526,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@Test
@EnableFlags(Flags.FLAG_MODES_API)
- public void automaticZenRuleToZenRule_updatesBitmaskAndValueForUserOrigin() {
+ public void updateAutomaticZenRule_fromUser_updatesBitmaskAndValue() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
.setZenPolicy(new ZenPolicy.Builder().build())
@@ -3571,84 +3558,21 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// UPDATE_ORIGIN_USER should change the bitmask and change the values.
assertThat(rule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
- assertThat(rule.getUserModifiedFields())
- .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
- assertThat(rule.getZenPolicy().getUserModifiedFields())
- .isEqualTo(ZenPolicy.FIELD_ALLOW_CHANNELS);
assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
- assertThat(rule.getDeviceEffects().getUserModifiedFields())
- .isEqualTo(ZenDeviceEffects.FIELD_GRAYSCALE);
assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
- }
- @Test
- @EnableFlags(Flags.FLAG_MODES_API)
- public void automaticZenRuleToZenRule_doesNotUpdateValuesForInitUserOrigin() {
- // Adds a starting rule with empty zen policies and device effects
- AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
- .setInterruptionFilter(INTERRUPTION_FILTER_ALL) // Already the default, no change
- .setZenPolicy(new ZenPolicy.Builder()
- .allowReminders(false)
- .build())
- .setDeviceEffects(new ZenDeviceEffects.Builder()
- .setShouldDisplayGrayscale(false)
- .build())
- .build();
- // Adds the rule using the user, to set user-modified bits.
- String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, UPDATE_ORIGIN_USER, "reason", Process.SYSTEM_UID);
- AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
- assertThat(rule.canUpdate()).isFalse();
- assertThat(rule.getUserModifiedFields()).isEqualTo(AutomaticZenRule.FIELD_NAME);
-
- ZenPolicy policy = new ZenPolicy.Builder(rule.getZenPolicy())
- .allowReminders(true)
- .build();
- ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder(rule.getDeviceEffects())
- .setShouldDisplayGrayscale(true)
- .build();
- AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
- .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
- .setZenPolicy(policy)
- .setDeviceEffects(deviceEffects)
- .build();
-
- // Attempts to update the rule with the AZR from origin init user.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_INIT_USER, "reason",
- Process.SYSTEM_UID);
- AutomaticZenRule unchangedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
-
- // UPDATE_ORIGIN_INIT_USER does not change the bitmask or values if rule is user modified.
- // TODO: b/318506692 - Remove once we check that INIT origins can't call add/updateAZR.
- assertThat(unchangedRule.getUserModifiedFields()).isEqualTo(rule.getUserModifiedFields());
- assertThat(unchangedRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL);
- assertThat(unchangedRule.getZenPolicy().getUserModifiedFields()).isEqualTo(
- rule.getZenPolicy().getUserModifiedFields());
- assertThat(unchangedRule.getZenPolicy().getPriorityCategoryReminders()).isEqualTo(
- ZenPolicy.STATE_DISALLOW);
- assertThat(unchangedRule.getDeviceEffects().getUserModifiedFields()).isEqualTo(
- rule.getDeviceEffects().getUserModifiedFields());
- assertThat(unchangedRule.getDeviceEffects().shouldDisplayGrayscale()).isFalse();
-
- // Creates a new rule with the AZR from origin init user.
- String newRuleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrUpdate, UPDATE_ORIGIN_INIT_USER, "reason", Process.SYSTEM_UID);
- AutomaticZenRule newRule = mZenModeHelper.getAutomaticZenRule(newRuleId);
-
- // UPDATE_ORIGIN_INIT_USER does change the values if the rule is new,
- // but does not update the bitmask.
- assertThat(newRule.getUserModifiedFields()).isEqualTo(0);
- assertThat(newRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_PRIORITY);
- assertThat(newRule.getZenPolicy().getUserModifiedFields()).isEqualTo(0);
- assertThat(newRule.getZenPolicy().getPriorityCategoryReminders())
- .isEqualTo(ZenPolicy.STATE_ALLOW);
- assertThat(newRule.getDeviceEffects().getUserModifiedFields()).isEqualTo(0);
- assertThat(newRule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+ ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+ assertThat(storedRule.userModifiedFields)
+ .isEqualTo(AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
+ assertThat(storedRule.zenPolicyUserModifiedFields)
+ .isEqualTo(ZenPolicy.FIELD_ALLOW_CHANNELS);
+ assertThat(storedRule.zenDeviceEffectsUserModifiedFields)
+ .isEqualTo(ZenDeviceEffects.FIELD_GRAYSCALE);
}
@Test
@EnableFlags(Flags.FLAG_MODES_API)
- public void automaticZenRuleToZenRule_updatesValuesForSystemUiOrigin() {
+ public void updateAutomaticZenRule_fromSystemUi_updatesValues() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_ALL)
@@ -3684,17 +3608,19 @@ public class ZenModeHelperTest extends UiServiceTestCase {
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
// UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI should change the value but NOT update the bitmask.
- assertThat(rule.getUserModifiedFields()).isEqualTo(0);
- assertThat(rule.getZenPolicy().getUserModifiedFields()).isEqualTo(0);
assertThat(rule.getZenPolicy().getPriorityCategoryReminders())
.isEqualTo(ZenPolicy.STATE_ALLOW);
- assertThat(rule.getDeviceEffects().getUserModifiedFields()).isEqualTo(0);
assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+
+ ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+ assertThat(storedRule.userModifiedFields).isEqualTo(0);
+ assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(0);
+ assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(0);
}
@Test
@EnableFlags(Flags.FLAG_MODES_API)
- public void automaticZenRuleToZenRule_updatesValuesIfRuleNotUserModified() {
+ public void updateAutomaticZenRule_fromApp_updatesValuesIfRuleNotUserModified() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_ALL)
@@ -3709,7 +3635,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
- assertThat(rule.canUpdate()).isTrue();
ZenPolicy policy = new ZenPolicy.Builder()
.allowReminders(true)
@@ -3717,57 +3642,59 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
.setShouldDisplayGrayscale(true)
.build();
- AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
+ AutomaticZenRule azrUpdate = new AutomaticZenRule.Builder(rule)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
.setZenPolicy(policy)
.setDeviceEffects(deviceEffects)
.build();
- // Since the rule is not already user modified, UPDATE_ORIGIN_UNKNOWN can modify the rule.
+ // Since the rule is not already user modified, UPDATE_ORIGIN_APP can modify the rule.
// The bitmask is not modified.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_UNKNOWN, "reason",
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azrUpdate, UPDATE_ORIGIN_APP, "reason",
Process.SYSTEM_UID);
- AutomaticZenRule unchangedRule = mZenModeHelper.getAutomaticZenRule(ruleId);
- assertThat(unchangedRule.getUserModifiedFields()).isEqualTo(rule.getUserModifiedFields());
- assertThat(unchangedRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
- assertThat(unchangedRule.getZenPolicy().getUserModifiedFields()).isEqualTo(
- rule.getZenPolicy().getUserModifiedFields());
- assertThat(unchangedRule.getZenPolicy().getPriorityCategoryReminders())
+ ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+ assertThat(storedRule.userModifiedFields).isEqualTo(0);
+
+ assertThat(storedRule.zenMode).isEqualTo(ZEN_MODE_ALARMS);
+ assertThat(storedRule.zenPolicy.getPriorityCategoryReminders())
.isEqualTo(ZenPolicy.STATE_ALLOW);
- assertThat(unchangedRule.getDeviceEffects().getUserModifiedFields()).isEqualTo(
- rule.getDeviceEffects().getUserModifiedFields());
- assertThat(unchangedRule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+ assertThat(storedRule.zenDeviceEffects.shouldDisplayGrayscale()).isTrue();
+ assertThat(storedRule.userModifiedFields).isEqualTo(0);
+ assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(0);
+ assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(0);
// Creates another rule, this time from user. This will have user modified bits set.
String ruleIdUser = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
azrBase, UPDATE_ORIGIN_USER, "reason", Process.SYSTEM_UID);
- AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser);
- assertThat(ruleUser.canUpdate()).isFalse();
+ storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleIdUser);
+ int ruleModifiedFields = storedRule.userModifiedFields;
+ int rulePolicyModifiedFields = storedRule.zenPolicyUserModifiedFields;
+ int ruleDeviceEffectsModifiedFields = storedRule.zenDeviceEffectsUserModifiedFields;
- // Zen rule update coming from unknown origin. This cannot fully update the rule, because
+ // Zen rule update coming from the app again. This cannot fully update the rule, because
// the rule is already considered user modified.
- mZenModeHelper.updateAutomaticZenRule(ruleIdUser, azrUpdate, UPDATE_ORIGIN_UNKNOWN,
+ mZenModeHelper.updateAutomaticZenRule(ruleIdUser, azrUpdate, UPDATE_ORIGIN_APP,
"reason", Process.SYSTEM_UID);
- ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser);
+ AutomaticZenRule ruleUser = mZenModeHelper.getAutomaticZenRule(ruleIdUser);
- // UPDATE_ORIGIN_UNKNOWN can only change the value if the rule is not already user modified,
+ // The app can only change the value if the rule is not already user modified,
// so the rule is not changed, and neither is the bitmask.
assertThat(ruleUser.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALL);
- // Interruption Filter All is the default value, so it's not included as a modified field.
- assertThat(ruleUser.getUserModifiedFields() | AutomaticZenRule.FIELD_NAME).isGreaterThan(0);
- assertThat(ruleUser.getZenPolicy().getUserModifiedFields()
- | ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS).isGreaterThan(0);
assertThat(ruleUser.getZenPolicy().getPriorityCategoryReminders())
.isEqualTo(ZenPolicy.STATE_DISALLOW);
- assertThat(ruleUser.getDeviceEffects().getUserModifiedFields()
- | ZenDeviceEffects.FIELD_GRAYSCALE).isGreaterThan(0);
assertThat(ruleUser.getDeviceEffects().shouldDisplayGrayscale()).isFalse();
+
+ storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleIdUser);
+ assertThat(storedRule.userModifiedFields).isEqualTo(ruleModifiedFields);
+ assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(rulePolicyModifiedFields);
+ assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(
+ ruleDeviceEffectsModifiedFields);
}
@Test
@EnableFlags(Flags.FLAG_MODES_API)
- public void automaticZenRuleToZenRule_updatesValuesIfRuleNew() {
+ public void addAutomaticZenRule_updatesValues() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
@@ -3778,21 +3705,22 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setShouldDisplayGrayscale(true)
.build())
.build();
- // Adds the rule using origin unknown, to show that a new rule is always allowed.
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
- azrBase, UPDATE_ORIGIN_UNKNOWN, "reason", Process.SYSTEM_UID);
+ azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
// The values are modified but the bitmask is not.
- assertThat(rule.canUpdate()).isTrue();
assertThat(rule.getZenPolicy().getPriorityCategoryReminders())
.isEqualTo(ZenPolicy.STATE_ALLOW);
assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
+
+ ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+ assertThat(storedRule.canBeUpdatedByApp()).isTrue();
}
@Test
@EnableFlags(Flags.FLAG_MODES_API)
- public void automaticZenRuleToZenRule_nullDeviceEffectsUpdate() {
+ public void updateAutomaticZenRule_nullDeviceEffectsUpdate() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
.setDeviceEffects(new ZenDeviceEffects.Builder().build())
@@ -3807,9 +3735,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.setDeviceEffects(null)
.build();
- // Zen rule update coming from unknown origin, but since the rule isn't already
+ // Zen rule update coming from app, but since the rule isn't already
// user modified, it can be updated.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_UNKNOWN, "reason",
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_APP, "reason",
Process.SYSTEM_UID);
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
@@ -3819,7 +3747,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
@Test
@EnableFlags(Flags.FLAG_MODES_API)
- public void automaticZenRuleToZenRule_nullPolicyUpdate() {
+ public void updateAutomaticZenRule_nullPolicyUpdate() {
// Adds a starting rule with empty zen policies and device effects
AutomaticZenRule azrBase = new AutomaticZenRule.Builder(NAME, CONDITION_ID)
.setZenPolicy(new ZenPolicy.Builder().build())
@@ -3828,16 +3756,15 @@ public class ZenModeHelperTest extends UiServiceTestCase {
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
- assertThat(rule.canUpdate()).isTrue();
AutomaticZenRule azr = new AutomaticZenRule.Builder(azrBase)
// Set zen policy to null
.setZenPolicy(null)
.build();
- // Zen rule update coming from unknown origin, but since the rule isn't already
+ // Zen rule update coming from app, but since the rule isn't already
// user modified, it can be updated.
- mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_UNKNOWN, "reason",
+ mZenModeHelper.updateAutomaticZenRule(ruleId, azr, UPDATE_ORIGIN_APP, "reason",
Process.SYSTEM_UID);
rule = mZenModeHelper.getAutomaticZenRule(ruleId);
@@ -3859,7 +3786,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
- assertThat(rule.canUpdate()).isTrue();
// Create a fully populated ZenPolicy.
ZenPolicy policy = new ZenPolicy.Builder()
@@ -3894,8 +3820,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// New ZenPolicy differs from the default config
assertThat(rule.getZenPolicy()).isNotNull();
assertThat(rule.getZenPolicy().getPriorityChannels()).isEqualTo(ZenPolicy.STATE_DISALLOW);
- assertThat(rule.canUpdate()).isFalse();
- assertThat(rule.getZenPolicy().getUserModifiedFields()).isEqualTo(
+
+ ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+ assertThat(storedRule.canBeUpdatedByApp()).isFalse();
+ assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(
ZenPolicy.FIELD_ALLOW_CHANNELS
| ZenPolicy.FIELD_PRIORITY_CATEGORY_REMINDERS
| ZenPolicy.FIELD_PRIORITY_CATEGORY_EVENTS
@@ -3918,7 +3846,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(),
azrBase, UPDATE_ORIGIN_APP, "reason", Process.SYSTEM_UID);
AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
- assertThat(rule.canUpdate()).isTrue();
ZenDeviceEffects deviceEffects = new ZenDeviceEffects.Builder()
.setShouldDisplayGrayscale(true)
@@ -3935,8 +3862,10 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// New ZenDeviceEffects is used; all fields considered set, since previously were null.
assertThat(rule.getDeviceEffects()).isNotNull();
assertThat(rule.getDeviceEffects().shouldDisplayGrayscale()).isTrue();
- assertThat(rule.canUpdate()).isFalse();
- assertThat(rule.getDeviceEffects().getUserModifiedFields()).isEqualTo(
+
+ ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+ assertThat(storedRule.canBeUpdatedByApp()).isFalse();
+ assertThat(storedRule.zenDeviceEffectsUserModifiedFields).isEqualTo(
ZenDeviceEffects.FIELD_GRAYSCALE);
}
@@ -4339,7 +4268,6 @@ public class ZenModeHelperTest extends UiServiceTestCase {
String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), rule,
UPDATE_ORIGIN_APP, "add it", CUSTOM_PKG_UID);
assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).getCreationTime()).isEqualTo(1000);
- assertThat(mZenModeHelper.getAutomaticZenRule(ruleId).canUpdate()).isTrue();
// User customizes it.
AutomaticZenRule userUpdate = new AutomaticZenRule.Builder(rule)
@@ -4371,9 +4299,11 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertThat(finalRule.getInterruptionFilter()).isEqualTo(INTERRUPTION_FILTER_ALARMS);
assertThat(finalRule.getZenPolicy().getPriorityCategoryRepeatCallers()).isEqualTo(
ZenPolicy.STATE_ALLOW);
- assertThat(finalRule.getUserModifiedFields()).isEqualTo(
+
+ ZenRule storedRule = mZenModeHelper.mConfig.automaticRules.get(ruleId);
+ assertThat(storedRule.userModifiedFields).isEqualTo(
AutomaticZenRule.FIELD_INTERRUPTION_FILTER);
- assertThat(finalRule.getZenPolicy().getUserModifiedFields()).isEqualTo(
+ assertThat(storedRule.zenPolicyUserModifiedFields).isEqualTo(
ZenPolicy.FIELD_PRIORITY_CATEGORY_REPEAT_CALLERS);
// Also, we discarded the "deleted rule" since we already used it for restoration.
@@ -4652,7 +4582,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZEN_MODE_IMPORTANT_INTERRUPTIONS);
assertThat(mZenModeHelper.mConfig.automaticRules.values())
- .comparingElementsUsing(IGNORE_TIMESTAMPS)
+ .comparingElementsUsing(IGNORE_METADATA)
.containsExactly(
expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
null, true));
@@ -4672,12 +4602,75 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZEN_MODE_ALARMS);
assertThat(mZenModeHelper.mConfig.automaticRules.values())
- .comparingElementsUsing(IGNORE_TIMESTAMPS)
+ .comparingElementsUsing(IGNORE_METADATA)
.containsExactly(
expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_ALARMS, null, true));
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void applyGlobalZenModeAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() {
+ mZenModeHelper.mConfig.automaticRules.clear();
+ String pkg = mContext.getPackageName();
+
+ // From app, call "setInterruptionFilter" and create and implicit rule.
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
+ assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
+ .isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+ // From user, update that rule's interruption filter.
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
+ .setInterruptionFilter(INTERRUPTION_FILTER_ALARMS)
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
+ Process.SYSTEM_UID);
+
+ // From app, call "setInterruptionFilter" again.
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+ ZEN_MODE_NO_INTERRUPTIONS);
+
+ // The app's update was ignored, and the user's update is still current, and the current
+ // mode is the one they chose.
+ assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
+ .isEqualTo(ZEN_MODE_ALARMS);
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_ALARMS);
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void applyGlobalZenModeAsImplicitZenRule_ruleCustomizedButNotFilter_updatesRule() {
+ mZenModeHelper.mConfig.automaticRules.clear();
+ String pkg = mContext.getPackageName();
+
+ // From app, call "setInterruptionFilter" and create and implicit rule.
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+ ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+ String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
+ assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
+ .isEqualTo(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
+
+ // From user, update something in that rule, but not the interruption filter.
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
+ .setName("Renamed")
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
+ Process.SYSTEM_UID);
+
+ // From app, call "setInterruptionFilter" again.
+ mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(pkg, CUSTOM_PKG_UID,
+ ZEN_MODE_NO_INTERRUPTIONS);
+
+ // The app's update was accepted, and the current mode is the one that they wanted.
+ assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenMode)
+ .isEqualTo(ZEN_MODE_NO_INTERRUPTIONS);
+ assertThat(mZenModeHelper.getZenMode()).isEqualTo(ZEN_MODE_NO_INTERRUPTIONS);
+ }
+
+ @Test
public void applyGlobalZenModeAsImplicitZenRule_modeOff_deactivatesImplicitRule() {
mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.mConfig.automaticRules.clear();
@@ -4747,8 +4740,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
Policy policy = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy,
- UPDATE_ORIGIN_APP);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, policy);
ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
.disallowAllSounds()
@@ -4758,7 +4750,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.allowPriorityChannels(true)
.build();
assertThat(mZenModeHelper.mConfig.automaticRules.values())
- .comparingElementsUsing(IGNORE_TIMESTAMPS)
+ .comparingElementsUsing(IGNORE_METADATA)
.containsExactly(
expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
expectedZenPolicy, /* conditionActive= */ null));
@@ -4773,14 +4765,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
PRIORITY_SENDERS_CONTACTS, PRIORITY_SENDERS_STARRED,
Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- original, UPDATE_ORIGIN_APP);
+ original);
// Change priorityCallSenders: contacts -> starred.
Policy updated = new Policy(PRIORITY_CATEGORY_CALLS | PRIORITY_CATEGORY_CONVERSATIONS,
PRIORITY_SENDERS_STARRED, PRIORITY_SENDERS_STARRED,
Policy.getAllSuppressedVisualEffects(), CONVERSATION_SENDERS_IMPORTANT);
- mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated,
- UPDATE_ORIGIN_APP);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, updated);
ZenPolicy expectedZenPolicy = new ZenPolicy.Builder()
.disallowAllSounds()
@@ -4790,20 +4781,87 @@ public class ZenModeHelperTest extends UiServiceTestCase {
.allowPriorityChannels(true)
.build();
assertThat(mZenModeHelper.mConfig.automaticRules.values())
- .comparingElementsUsing(IGNORE_TIMESTAMPS)
+ .comparingElementsUsing(IGNORE_METADATA)
.containsExactly(
expectedImplicitRule(CUSTOM_PKG_NAME, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
expectedZenPolicy, /* conditionActive= */ null));
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void applyGlobalPolicyAsImplicitZenRule_ruleCustomized_doesNotUpdateRule() {
+ mZenModeHelper.mConfig.automaticRules.clear();
+ String pkg = mContext.getPackageName();
+
+ // From app, call "setNotificationPolicy" and create and implicit rule.
+ Policy originalPolicy = new Policy(PRIORITY_CATEGORY_MEDIA, 0, 0);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy);
+ String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
+
+ // From user, update that rule's policy.
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ ZenPolicy userUpdateZenPolicy = new ZenPolicy.Builder().disallowAllSounds()
+ .allowAlarms(true).build();
+ AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
+ .setZenPolicy(userUpdateZenPolicy)
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
+ Process.SYSTEM_UID);
+
+ // From app, call "setNotificationPolicy" again.
+ Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, appUpdatePolicy);
+
+ // The app's update was ignored, and the user's update is still current.
+ assertThat(mZenModeHelper.mConfig.automaticRules.values())
+ .comparingElementsUsing(IGNORE_METADATA)
+ .containsExactly(
+ expectedImplicitRule(pkg, ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+ userUpdateZenPolicy,
+ /* conditionActive= */ null));
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_MODES_API)
+ public void applyGlobalPolicyAsImplicitZenRule_ruleCustomizedButNotZenPolicy_updatesRule() {
+ mZenModeHelper.mConfig.automaticRules.clear();
+ String pkg = mContext.getPackageName();
+
+ // From app, call "setNotificationPolicy" and create and implicit rule.
+ Policy originalPolicy = new Policy(PRIORITY_CATEGORY_MEDIA, 0, 0);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, originalPolicy);
+ String ruleId = getOnlyElement(mZenModeHelper.mConfig.automaticRules.keySet());
+
+ // From user, update something in that rule, but not the ZenPolicy.
+ AutomaticZenRule rule = mZenModeHelper.getAutomaticZenRule(ruleId);
+ AutomaticZenRule userUpdateRule = new AutomaticZenRule.Builder(rule)
+ .setName("Rule renamed, not touching policy")
+ .build();
+ mZenModeHelper.updateAutomaticZenRule(ruleId, userUpdateRule, UPDATE_ORIGIN_USER, "reason",
+ Process.SYSTEM_UID);
+
+ // From app, call "setNotificationPolicy" again.
+ Policy appUpdatePolicy = new Policy(PRIORITY_CATEGORY_SYSTEM, 0, 0);
+ mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(pkg, CUSTOM_PKG_UID, appUpdatePolicy);
+
+ // The app's update was applied.
+ ZenPolicy appsSecondZenPolicy = new ZenPolicy.Builder()
+ .disallowAllSounds()
+ .allowSystem(true)
+ .allowPriorityChannels(true)
+ .build();
+ assertThat(getOnlyElement(mZenModeHelper.mConfig.automaticRules.values()).zenPolicy)
+ .isEqualTo(appsSecondZenPolicy);
+ }
+
+ @Test
public void applyGlobalPolicyAsImplicitZenRule_flagOff_ignored() {
mSetFlagsRule.disableFlags(android.app.Flags.FLAG_MODES_API);
mZenModeHelper.mConfig.automaticRules.clear();
withoutWtfCrash(
() -> mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME,
- CUSTOM_PKG_UID, new Policy(0, 0, 0), UPDATE_ORIGIN_APP));
+ CUSTOM_PKG_UID, new Policy(0, 0, 0)));
assertThat(mZenModeHelper.mConfig.automaticRules).isEmpty();
}
@@ -4816,7 +4874,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
Policy.getAllSuppressedVisualEffects(), STATE_FALSE,
CONVERSATION_SENDERS_IMPORTANT);
mZenModeHelper.applyGlobalPolicyAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID,
- writtenPolicy, UPDATE_ORIGIN_APP);
+ writtenPolicy);
Policy readPolicy = mZenModeHelper.getNotificationPolicyFromImplicitZenRule(
CUSTOM_PKG_NAME);
@@ -4856,7 +4914,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertThat(readPolicy.allowConversations()).isFalse();
}
- private static final Correspondence<ZenRule, ZenRule> IGNORE_TIMESTAMPS =
+ private static final Correspondence<ZenRule, ZenRule> IGNORE_METADATA =
Correspondence.transforming(zr -> {
Parcel p = Parcel.obtain();
try {
@@ -4864,12 +4922,15 @@ public class ZenModeHelperTest extends UiServiceTestCase {
p.setDataPosition(0);
ZenRule copy = new ZenRule(p);
copy.creationTime = 0;
+ copy.userModifiedFields = 0;
+ copy.zenPolicyUserModifiedFields = 0;
+ copy.zenDeviceEffectsUserModifiedFields = 0;
return copy;
} finally {
p.recycle();
}
},
- "Ignoring timestamps");
+ "Ignoring timestamp and userModifiedFields");
private ZenRule expectedImplicitRule(String ownerPkg, int zenMode, ZenPolicy policy,
@Nullable Boolean conditionActive) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
index 7941eb4d2090..4ed55df7775c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
@@ -645,38 +645,12 @@ public class ZenPolicyTest extends UiServiceTestCase {
}
@Test
- public void testFromParcel() {
- ZenPolicy.Builder builder = new ZenPolicy.Builder();
- builder.setUserModifiedFields(10);
-
- ZenPolicy policy = builder.build();
- assertThat(policy.getUserModifiedFields()).isEqualTo(10);
-
- Parcel parcel = Parcel.obtain();
- policy.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
-
- ZenPolicy fromParcel = ZenPolicy.CREATOR.createFromParcel(parcel);
- assertThat(fromParcel.getUserModifiedFields()).isEqualTo(10);
- }
-
- @Test
- public void testPolicy_userModifiedFields() {
- ZenPolicy.Builder builder = new ZenPolicy.Builder();
- builder.setUserModifiedFields(10);
- assertThat(builder.build().getUserModifiedFields()).isEqualTo(10);
-
- builder.setUserModifiedFields(0);
- assertThat(builder.build().getUserModifiedFields()).isEqualTo(0);
- }
-
- @Test
public void testPolicyBuilder_constructFromPolicy() {
ZenPolicy.Builder builder = new ZenPolicy.Builder();
ZenPolicy policy = builder.allowRepeatCallers(true).allowAlarms(false)
.showLights(true).showBadges(false)
.allowPriorityChannels(true)
- .setUserModifiedFields(20).build();
+ .build();
ZenPolicy newPolicy = new ZenPolicy.Builder(policy).build();
@@ -689,7 +663,6 @@ public class ZenPolicyTest extends UiServiceTestCase {
assertThat(newPolicy.getVisualEffectPeek()).isEqualTo(ZenPolicy.STATE_UNSET);
assertThat(newPolicy.getPriorityChannels()).isEqualTo(ZenPolicy.STATE_ALLOW);
- assertThat(newPolicy.getUserModifiedFields()).isEqualTo(20);
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
index 71dbc57e5065..298637266cc3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SensitiveContentPackagesTest.java
@@ -20,6 +20,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
import androidx.test.filters.SmallTest;
@@ -28,7 +29,6 @@ import com.android.server.wm.SensitiveContentPackages.PackageInfo;
import org.junit.After;
import org.junit.Test;
-import java.util.Collections;
import java.util.Set;
/**
@@ -52,18 +52,21 @@ public class SensitiveContentPackagesTest {
@After
public void tearDown() {
- mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ mSensitiveContentPackages.clearBlockedApps();
}
@Test
- public void setShouldBlockScreenCaptureForApp() {
- Set<PackageInfo> blockedApps =
+ public void addBlockScreenCaptureForApps() {
+ ArraySet<PackageInfo> blockedApps = new ArraySet(
Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
new PackageInfo(APP_PKG_1, APP_UID_2),
new PackageInfo(APP_PKG_2, APP_UID_1),
- new PackageInfo(APP_PKG_2, APP_UID_2));
+ new PackageInfo(APP_PKG_2, APP_UID_2)
+ ));
- mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedApps);
+ boolean modified = mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+
+ assertTrue(modified);
assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
@@ -79,15 +82,93 @@ public class SensitiveContentPackagesTest {
}
@Test
- public void setShouldBlockScreenCaptureForApp_empty() {
- Set<PackageInfo> blockedApps =
+ public void addBlockScreenCaptureForApps_addedTwice() {
+ ArraySet<PackageInfo> blockedApps = new ArraySet(
Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
new PackageInfo(APP_PKG_1, APP_UID_2),
new PackageInfo(APP_PKG_2, APP_UID_1),
- new PackageInfo(APP_PKG_2, APP_UID_2));
+ new PackageInfo(APP_PKG_2, APP_UID_2)
+ ));
+
+ mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+ boolean modified = mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+
+ assertFalse(modified);
+
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
+
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
+
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+ }
+
+ @Test
+ public void addBlockScreenCaptureForApps_withPartialPreviousPackages() {
+ ArraySet<PackageInfo> blockedApps = new ArraySet(
+ Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
+ new PackageInfo(APP_PKG_1, APP_UID_2),
+ new PackageInfo(APP_PKG_2, APP_UID_1),
+ new PackageInfo(APP_PKG_2, APP_UID_2)
+ ));
+
+ mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+ boolean modified = mSensitiveContentPackages
+ .addBlockScreenCaptureForApps(
+ new ArraySet(Set.of(new PackageInfo(APP_PKG_3, APP_UID_1))));
+
+ assertTrue(modified);
+
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
+
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
+
+ assertTrue(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+ }
+
+ @Test
+ public void clearBlockedApps() {
+ ArraySet<PackageInfo> blockedApps = new ArraySet(
+ Set.of(new PackageInfo(APP_PKG_1, APP_UID_1),
+ new PackageInfo(APP_PKG_1, APP_UID_2),
+ new PackageInfo(APP_PKG_2, APP_UID_1),
+ new PackageInfo(APP_PKG_2, APP_UID_2)
+ ));
+
+ mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedApps);
+ boolean modified = mSensitiveContentPackages.clearBlockedApps();
+
+ assertTrue(modified);
+
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_3));
+
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_1));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_2, APP_UID_3));
+
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_1));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_2));
+ assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_3, APP_UID_3));
+ }
+
+ @Test
+ public void clearBlockedApps_alreadyEmpty() {
+ boolean modified = mSensitiveContentPackages.clearBlockedApps();
- mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedApps);
- mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ assertFalse(modified);
assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_1));
assertFalse(mSensitiveContentPackages.shouldBlockScreenCaptureForApp(APP_PKG_1, APP_UID_2));
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index a1cc8d5d9188..fe9d83776ad9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -115,7 +115,6 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
-import java.util.Collections;
/**
* Build/Install/Run:
@@ -139,7 +138,7 @@ public class WindowManagerServiceTests extends WindowTestsBase {
@After
public void tearDown() {
- mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ mWm.mSensitiveContentPackages.clearBlockedApps();
}
@Test
@@ -824,7 +823,7 @@ public class WindowManagerServiceTests extends WindowTestsBase {
}
@Test
- public void setShouldBlockScreenCaptureForApp() {
+ public void addBlockScreenCaptureForApps() {
String testPackage = "test";
int ownerId1 = 20;
int ownerId2 = 21;
@@ -833,7 +832,7 @@ public class WindowManagerServiceTests extends WindowTestsBase {
blockedPackages.add(blockedPackage);
WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
- wmInternal.setShouldBlockScreenCaptureForApp(blockedPackages);
+ wmInternal.addBlockScreenCaptureForApps(blockedPackages);
assertTrue(mWm.mSensitiveContentPackages
.shouldBlockScreenCaptureForApp(testPackage, ownerId1));
@@ -843,7 +842,47 @@ public class WindowManagerServiceTests extends WindowTestsBase {
}
@Test
- public void setShouldBlockScreenCaptureForApp_emptySet_clearsCache() {
+ public void addBlockScreenCaptureForApps_duplicate_verifyNoRefresh() {
+ String testPackage = "test";
+ int ownerId1 = 20;
+ int ownerId2 = 21;
+ PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
+ ArraySet<PackageInfo> blockedPackages = new ArraySet();
+ blockedPackages.add(blockedPackage);
+
+ WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+ wmInternal.addBlockScreenCaptureForApps(blockedPackages);
+ wmInternal.addBlockScreenCaptureForApps(blockedPackages);
+
+ verify(mWm, times(1)).refreshScreenCaptureDisabled();
+ }
+
+ @Test
+ public void addBlockScreenCaptureForApps_notDuplicate_verifyRefresh() {
+ String testPackage = "test";
+ int ownerId1 = 20;
+ int ownerId2 = 21;
+ PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
+ PackageInfo blockedPackage2 = new PackageInfo(testPackage, ownerId2);
+ ArraySet<PackageInfo> blockedPackages = new ArraySet();
+ blockedPackages.add(blockedPackage);
+ ArraySet<PackageInfo> blockedPackages2 = new ArraySet();
+ blockedPackages2.add(blockedPackage);
+ blockedPackages2.add(blockedPackage2);
+
+ WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+ wmInternal.addBlockScreenCaptureForApps(blockedPackages);
+ wmInternal.addBlockScreenCaptureForApps(blockedPackages2);
+
+ assertTrue(mWm.mSensitiveContentPackages
+ .shouldBlockScreenCaptureForApp(testPackage, ownerId1));
+ assertTrue(mWm.mSensitiveContentPackages
+ .shouldBlockScreenCaptureForApp(testPackage, ownerId2));
+ verify(mWm, times(2)).refreshScreenCaptureDisabled();
+ }
+
+ @Test
+ public void clearBlockedApps_clearsCache() {
String testPackage = "test";
int ownerId1 = 20;
PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
@@ -851,8 +890,8 @@ public class WindowManagerServiceTests extends WindowTestsBase {
blockedPackages.add(blockedPackage);
WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
- wmInternal.setShouldBlockScreenCaptureForApp(blockedPackages);
- wmInternal.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ wmInternal.addBlockScreenCaptureForApps(blockedPackages);
+ wmInternal.clearBlockedApps();
assertFalse(mWm.mSensitiveContentPackages
.shouldBlockScreenCaptureForApp(testPackage, ownerId1));
@@ -860,6 +899,14 @@ public class WindowManagerServiceTests extends WindowTestsBase {
}
@Test
+ public void clearBlockedApps_alreadyEmpty_verifyNoRefresh() {
+ WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
+ wmInternal.clearBlockedApps();
+
+ verify(mWm, never()).refreshScreenCaptureDisabled();
+ }
+
+ @Test
public void testisLetterboxBackgroundMultiColored() {
assertThat(setupLetterboxConfigurationWithBackgroundType(
LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING)).isTrue();
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 fb4edfacb8e3..a0562aa2f710 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -136,7 +136,7 @@ public class WindowStateTests extends WindowTestsBase {
@After
public void tearDown() {
- mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(Collections.emptySet());
+ mWm.mSensitiveContentPackages.clearBlockedApps();
}
@Test
@@ -1398,7 +1398,7 @@ public class WindowStateTests extends WindowTestsBase {
PackageInfo blockedPackage = new PackageInfo(testPackage, ownerId1);
ArraySet<PackageInfo> blockedPackages = new ArraySet();
blockedPackages.add(blockedPackage);
- mWm.mSensitiveContentPackages.setShouldBlockScreenCaptureForApp(blockedPackages);
+ mWm.mSensitiveContentPackages.addBlockScreenCaptureForApps(blockedPackages);
assertTrue(window1.isSecureLocked());
assertFalse(window2.isSecureLocked());
diff --git a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java
index 336bfdd0fb14..a8fd6f29f862 100644
--- a/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java
+++ b/services/usage/java/com/android/server/usage/BroadcastResponseStatsLogger.java
@@ -35,11 +35,13 @@ import android.util.Slog;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.Keep;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.RingBuffer;
import com.android.server.usage.BroadcastResponseStatsTracker.NotificationEventType;
+import java.util.function.IntFunction;
+import java.util.function.Supplier;
+
public class BroadcastResponseStatsLogger {
private static final int MAX_LOG_SIZE =
@@ -49,10 +51,10 @@ public class BroadcastResponseStatsLogger {
@GuardedBy("mLock")
private final LogBuffer mBroadcastEventsBuffer = new LogBuffer(
- BroadcastEvent.class, MAX_LOG_SIZE);
+ BroadcastEvent::new, BroadcastEvent[]::new, MAX_LOG_SIZE);
@GuardedBy("mLock")
private final LogBuffer mNotificationEventsBuffer = new LogBuffer(
- NotificationEvent.class, MAX_LOG_SIZE);
+ NotificationEvent::new, NotificationEvent[]::new, MAX_LOG_SIZE);
void logBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage,
UserHandle targetUser, long idForResponseEvent,
@@ -96,8 +98,8 @@ public class BroadcastResponseStatsLogger {
private static final class LogBuffer<T extends Data> extends RingBuffer<T> {
- LogBuffer(Class<T> classType, int capacity) {
- super(classType, capacity);
+ LogBuffer(Supplier<T> newItem, IntFunction<T[]> newBacking, int capacity) {
+ super(newItem, newBacking, capacity);
}
void logBroadcastDispatchEvent(int sourceUid, @NonNull String targetPackage,
@@ -179,8 +181,7 @@ public class BroadcastResponseStatsLogger {
}
}
- @Keep
- public static final class BroadcastEvent implements Data {
+ private static final class BroadcastEvent implements Data {
public int sourceUid;
public int targetUserId;
public int targetUidProcessState;
@@ -200,8 +201,7 @@ public class BroadcastResponseStatsLogger {
}
}
- @Keep
- public static final class NotificationEvent implements Data {
+ private static final class NotificationEvent implements Data {
public int type;
public String packageName;
public int userId;
@@ -218,7 +218,7 @@ public class BroadcastResponseStatsLogger {
}
}
- public interface Data {
+ private interface Data {
void reset();
}
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 08f719e91da9..9fc64feb4b25 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2440,7 +2440,7 @@ public class UsageStatsService extends SystemService implements
}
return queryEventsHelper(userId, query.getBeginTimeMillis(),
- query.getEndTimeMillis(), callingPackage, query.getEventTypeFilter());
+ query.getEndTimeMillis(), callingPackage, query.getEventTypes());
}
@Override
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
index 1df7012c44f8..49ad46131b0d 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/PhoneCallStateHandler.java
@@ -62,7 +62,8 @@ public class PhoneCallStateHandler {
SubscriptionManager subscriptionManager,
TelephonyManager telephonyManager,
Callback callback) {
- mSubscriptionManager = Objects.requireNonNull(subscriptionManager);
+ mSubscriptionManager = Objects.requireNonNull(subscriptionManager)
+ .createForAllUserProfiles();
mTelephonyManager = Objects.requireNonNull(telephonyManager);
mCallback = Objects.requireNonNull(callback);
mSubscriptionManager.addOnSubscriptionsChangedListener(
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 57b13e960093..e81f48280e46 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1049,8 +1049,17 @@ public class TelecomManager {
public static final int PRESENTATION_UNAVAILABLE = 5;
+ /**
+ * Controls audio route for video calls.
+ * 0 - Use the default audio routing strategy.
+ * 1 - Disable the speaker. Route the audio to Headset or Bluetooth
+ * or Earpiece, based on the default audio routing strategy.
+ * @hide
+ */
+ public static final String PROPERTY_VIDEOCALL_AUDIO_OUTPUT = "persist.radio.call.audio.output";
+
/*
- * Values for the adb property "persist.radio.videocall.audio.output"
+ * Values for the adb property "persist.radio.call.audio.output"
*/
/** @hide */
public static final int AUDIO_OUTPUT_ENABLE_SPEAKER = 0;
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 2a6ac98b4d98..86eed2f6a090 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -601,8 +601,9 @@ public final class TelephonyPermissions {
*
* @return true if caller has ACCESS_LAST_KNOWN_CELL_ID permission else false.
*/
+ @RequiresPermission(Manifest.permission.ACCESS_LAST_KNOWN_CELL_ID)
public static boolean checkLastKnownCellIdAccessPermission(Context context) {
- return context.checkCallingOrSelfPermission("android.permission.ACCESS_LAST_KNOWN_CELL_ID")
+ return context.checkCallingOrSelfPermission(Manifest.permission.ACCESS_LAST_KNOWN_CELL_ID)
== PackageManager.PERMISSION_GRANTED;
}
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 73c26a3e5fc9..1badf674c8ce 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9694,6 +9694,27 @@ public class CarrierConfigManager {
"remove_satellite_plmn_in_manual_network_scan_bool";
/**
+ * An integer key holds the time interval for refreshing or re-querying the satellite
+ * entitlement status from the entitlement server to ensure it is the latest.
+ *
+ * The default value is 30 days (1 month).
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public static final String KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT =
+ "satellite_entitlement_status_refresh_days_int";
+
+ /**
+ * This configuration enables device to query the entitlement server to get the satellite
+ * configuration.
+ * This will need agreement the carrier before enabling this flag.
+ *
+ * The default value is false.
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public static final String KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL =
+ "satellite_entitlement_supported_bool";
+
+ /**
* Indicating whether DUN APN should be disabled when the device is roaming. In that case,
* the default APN (i.e. internet) will be used for tethering.
*
@@ -10799,6 +10820,8 @@ public class CarrierConfigManager {
sDefaults.putInt(KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT,
CellSignalStrengthLte.USE_RSRP);
sDefaults.putBoolean(KEY_REMOVE_SATELLITE_PLMN_IN_MANUAL_NETWORK_SCAN_BOOL, true);
+ sDefaults.putInt(KEY_SATELLITE_ENTITLEMENT_STATUS_REFRESH_DAYS_INT, 30);
+ sDefaults.putBoolean(KEY_SATELLITE_ENTITLEMENT_SUPPORTED_BOOL, false);
sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false);
sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, "");
sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false);
diff --git a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
index c902016d2cf0..57209eb68de8 100644
--- a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
+++ b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
@@ -129,11 +129,15 @@ public final class SecurityAlgorithmUpdate implements Parcelable {
public static final int CONNECTION_EVENT_NAS_SIGNALLING_LTE = 4;
public static final int CONNECTION_EVENT_AS_SIGNALLING_LTE = 5;
public static final int CONNECTION_EVENT_VOLTE_SIP = 6;
- public static final int CONNECTION_EVENT_VOLTE_RTP = 7;
- public static final int CONNECTION_EVENT_NAS_SIGNALLING_5G = 8;
- public static final int CONNECTION_EVENT_AS_SIGNALLING_5G = 9;
- public static final int CONNECTION_EVENT_VONR_SIP = 10;
- public static final int CONNECTION_EVENT_VONR_RTP = 11;
+ public static final int CONNECTION_EVENT_VOLTE_SIP_SOS = 7;
+ public static final int CONNECTION_EVENT_VOLTE_RTP = 8;
+ public static final int CONNECTION_EVENT_VOLTE_RTP_SOS = 9;
+ public static final int CONNECTION_EVENT_NAS_SIGNALLING_5G = 10;
+ public static final int CONNECTION_EVENT_AS_SIGNALLING_5G = 11;
+ public static final int CONNECTION_EVENT_VONR_SIP = 12;
+ public static final int CONNECTION_EVENT_VONR_SIP_SOS = 13;
+ public static final int CONNECTION_EVENT_VONR_RTP = 14;
+ public static final int CONNECTION_EVENT_VONR_RTP_SOS = 15;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -141,9 +145,11 @@ public final class SecurityAlgorithmUpdate implements Parcelable {
CONNECTION_EVENT_PS_SIGNALLING_GPRS, CONNECTION_EVENT_CS_SIGNALLING_3G,
CONNECTION_EVENT_PS_SIGNALLING_3G, CONNECTION_EVENT_NAS_SIGNALLING_LTE,
CONNECTION_EVENT_AS_SIGNALLING_LTE, CONNECTION_EVENT_VOLTE_SIP,
- CONNECTION_EVENT_VOLTE_RTP, CONNECTION_EVENT_NAS_SIGNALLING_5G,
+ CONNECTION_EVENT_VOLTE_SIP_SOS, CONNECTION_EVENT_VOLTE_RTP,
+ CONNECTION_EVENT_VOLTE_RTP_SOS, CONNECTION_EVENT_NAS_SIGNALLING_5G,
CONNECTION_EVENT_AS_SIGNALLING_5G, CONNECTION_EVENT_VONR_SIP,
- CONNECTION_EVENT_VONR_RTP})
+ CONNECTION_EVENT_VONR_SIP_SOS, CONNECTION_EVENT_VONR_RTP,
+ CONNECTION_EVENT_VONR_RTP_SOS})
public @interface ConnectionEvent {
}
@@ -169,6 +175,7 @@ public final class SecurityAlgorithmUpdate implements Parcelable {
public static final int SECURITY_ALGORITHM_NEA1 = 56;
public static final int SECURITY_ALGORITHM_NEA2 = 57;
public static final int SECURITY_ALGORITHM_NEA3 = 58;
+ public static final int SECURITY_ALGORITHM_SIP_NO_IPSEC_CONFIG = 66;
public static final int SECURITY_ALGORITHM_IMS_NULL = 67;
public static final int SECURITY_ALGORITHM_SIP_NULL = 68;
public static final int SECURITY_ALGORITHM_AES_GCM = 69;
@@ -178,6 +185,7 @@ public final class SecurityAlgorithmUpdate implements Parcelable {
public static final int SECURITY_ALGORITHM_AES_EDE3_CBC = 73;
public static final int SECURITY_ALGORITHM_HMAC_SHA1_96 = 74;
public static final int SECURITY_ALGORITHM_HMAC_MD5_96 = 75;
+ public static final int SECURITY_ALGORITHM_RTP = 85;
public static final int SECURITY_ALGORITHM_SRTP_NULL = 86;
public static final int SECURITY_ALGORITHM_SRTP_AES_COUNTER = 87;
public static final int SECURITY_ALGORITHM_SRTP_AES_F8 = 88;
@@ -199,15 +207,16 @@ public final class SecurityAlgorithmUpdate implements Parcelable {
SECURITY_ALGORITHM_UEA2, SECURITY_ALGORITHM_EEA0, SECURITY_ALGORITHM_EEA1,
SECURITY_ALGORITHM_EEA2, SECURITY_ALGORITHM_EEA3, SECURITY_ALGORITHM_NEA0,
SECURITY_ALGORITHM_NEA1, SECURITY_ALGORITHM_NEA2, SECURITY_ALGORITHM_NEA3,
- SECURITY_ALGORITHM_IMS_NULL, SECURITY_ALGORITHM_SIP_NULL, SECURITY_ALGORITHM_AES_GCM,
+ SECURITY_ALGORITHM_SIP_NO_IPSEC_CONFIG, SECURITY_ALGORITHM_IMS_NULL,
+ SECURITY_ALGORITHM_SIP_NULL, SECURITY_ALGORITHM_AES_GCM,
SECURITY_ALGORITHM_AES_GMAC, SECURITY_ALGORITHM_AES_CBC,
SECURITY_ALGORITHM_DES_EDE3_CBC, SECURITY_ALGORITHM_AES_EDE3_CBC,
SECURITY_ALGORITHM_HMAC_SHA1_96, SECURITY_ALGORITHM_HMAC_MD5_96,
- SECURITY_ALGORITHM_SRTP_NULL, SECURITY_ALGORITHM_SRTP_AES_COUNTER,
- SECURITY_ALGORITHM_SRTP_AES_F8, SECURITY_ALGORITHM_SRTP_HMAC_SHA1,
- SECURITY_ALGORITHM_ENCR_AES_GCM_16, SECURITY_ALGORITHM_ENCR_AES_CBC,
- SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128, SECURITY_ALGORITHM_UNKNOWN,
- SECURITY_ALGORITHM_OTHER, SECURITY_ALGORITHM_ORYX})
+ SECURITY_ALGORITHM_RTP, SECURITY_ALGORITHM_SRTP_NULL,
+ SECURITY_ALGORITHM_SRTP_AES_COUNTER, SECURITY_ALGORITHM_SRTP_AES_F8,
+ SECURITY_ALGORITHM_SRTP_HMAC_SHA1, SECURITY_ALGORITHM_ENCR_AES_GCM_16,
+ SECURITY_ALGORITHM_ENCR_AES_CBC, SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128,
+ SECURITY_ALGORITHM_UNKNOWN, SECURITY_ALGORITHM_OTHER, SECURITY_ALGORITHM_ORYX})
public @interface SecurityAlgorithm {
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index e4ea479fb4d6..9ec5f7a77b2c 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -18483,8 +18483,8 @@ public class TelephonyManager {
/**
* Get last known cell identity.
- * Require {@link android.Manifest.permission#ACCESS_FINE_LOCATION} and
- * com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID, otherwise throws SecurityException.
+ * Require appropriate permissions, otherwise throws SecurityException.
+ *
* If there is current registered network this value will be same as the registered cell
* identity. If the device goes out of service the previous cell identity is cached and
* will be returned. If the cache age of the Cell identity is more than 24 hours
@@ -18492,6 +18492,8 @@ public class TelephonyManager {
* @return last known cell identity {@CellIdentity}.
* @hide
*/
+ @SystemApi
+ @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
@RequiresPermission(allOf = {Manifest.permission.ACCESS_FINE_LOCATION,
"com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"})
public @Nullable CellIdentity getLastKnownCellIdentity() {
@@ -18569,13 +18571,11 @@ public class TelephonyManager {
* an overall "device can make voice calls" boolean, they can use {@link
* ServiceState#getNetworkRegistrationInfo} to check CS registration state.
*
- * <p>TODO(b/215240050) In the future, this API will be removed and replaced with a new superset
- * API to disentangle the "true" {@link ServiceState} meaning of "this is the connection status
- * to the tower" from IMS registration state and over-the-top voice calling capabilities.
- *
* @hide
*/
@TestApi
+ @SystemApi
+ @FlaggedApi(com.android.server.telecom.flags.Flags.FLAG_TELECOM_RESOLVE_HIDDEN_DEPENDENCIES)
@RequiresPermission(Manifest.permission.BIND_TELECOM_CONNECTION_SERVICE)
public void setVoiceServiceStateOverride(boolean hasService) {
try {
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index 4c37f7d3184c..b84ff2977b34 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -16,6 +16,7 @@
package android.telephony.ims;
+import android.annotation.FlaggedApi;
import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -46,6 +47,7 @@ import android.util.SparseBooleanArray;
import com.android.ims.internal.IImsFeatureStatusCallback;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.util.TelephonyUtils;
import java.lang.annotation.Retention;
@@ -152,12 +154,36 @@ public class ImsService extends Service {
public static final long CAPABILITY_TERMINAL_BASED_CALL_WAITING = 1 << 2;
/**
+ * This ImsService supports the capability to manage calls on multiple subscriptions at the same
+ * time.
+ * <p>
+ * When set, this ImsService supports managing calls on multiple subscriptions at the same time
+ * for all WLAN network configurations. Telephony will allow new outgoing/incoming IMS calls to
+ * be set up on other subscriptions while there is an ongoing call. The ImsService must also
+ * support managing calls on WWAN + WWAN configurations whenever the modem also reports
+ * simultaneous calling availability, which can be listened to using the
+ * {@link android.telephony.TelephonyCallback.SimultaneousCellularCallingSupportListener} API.
+ * Telephony will only allow additional ongoing/incoming IMS calls on another subscription to be
+ * set up on WWAN + WWAN configurations when the modem reports that simultaneous cellular
+ * calling is allowed at the current time on both subscriptions where there are ongoing calls.
+ * <p>
+ * When unset (default), this ImsService can not support calls on multiple subscriptions at the
+ * same time for any WLAN or WWAN configurations, so pending outgoing call placed on another
+ * cellular subscription while there is an ongoing call will be cancelled by Telephony.
+ * Similarly, any incoming call notification on another cellular subscription while there is an
+ * ongoing call will be rejected.
+ * @hide TODO: move this to system API when we have a backing implementation + CTS testing
+ */
+ @FlaggedApi(Flags.FLAG_SIMULTANEOUS_CALLING_INDICATIONS)
+ public static final long CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING = 1 << 3;
+
+ /**
* Used for internal correctness checks of capabilities set by the ImsService implementation and
* tracks the index of the largest defined flag in the capabilities long.
* @hide
*/
public static final long CAPABILITY_MAX_INDEX =
- Long.numberOfTrailingZeros(CAPABILITY_TERMINAL_BASED_CALL_WAITING);
+ Long.numberOfTrailingZeros(CAPABILITY_SUPPORTS_SIMULTANEOUS_CALLING);
/**
* @hide
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 3b0397b6e480..70047a6feb9c 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -928,10 +928,19 @@ public final class SatelliteManager {
@FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION = 1;
+ /**
+ * Satellite communication restricted by entitlement server. This can be triggered based on
+ * the EntitlementStatus value received from the entitlement server to enable or disable
+ * satellite.
+ */
+ @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ public static final int SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT = 2;
+
/** @hide */
@IntDef(prefix = "SATELLITE_COMMUNICATION_RESTRICTION_REASON_", value = {
SATELLITE_COMMUNICATION_RESTRICTION_REASON_USER,
- SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION
+ SATELLITE_COMMUNICATION_RESTRICTION_REASON_GEOLOCATION,
+ SATELLITE_COMMUNICATION_RESTRICTION_REASON_ENTITLEMENT
})
@Retention(RetentionPolicy.SOURCE)
public @interface SatelliteCommunicationRestrictionReason {}
diff --git a/test-base/Android.bp b/test-base/Android.bp
index 527159a78ebf..70a95400bd9e 100644
--- a/test-base/Android.bp
+++ b/test-base/Android.bp
@@ -120,12 +120,13 @@ filegroup {
path: "src",
}
-// Make the current.txt available for use by the cts/tests/signature tests.
+// Make the current.txt available for use by the cts/tests/signature and /vendor tests.
// ========================================================================
filegroup {
name: "android-test-base-current.txt",
visibility: [
"//cts/tests/signature/api",
+ "//vendor:__subpackages__",
],
srcs: [
"api/current.txt",
diff --git a/test-mock/Android.bp b/test-mock/Android.bp
index 2ff74132ffbb..f37d2d17973e 100644
--- a/test-mock/Android.bp
+++ b/test-mock/Android.bp
@@ -77,12 +77,13 @@ android_ravenwood_test {
auto_gen_config: true,
}
-// Make the current.txt available for use by the cts/tests/signature tests.
+// Make the current.txt available for use by the cts/tests/signature and /vendor tests.
// ========================================================================
filegroup {
name: "android-test-mock-current.txt",
visibility: [
"//cts/tests/signature/api",
+ "//vendor:__subpackages__",
],
srcs: [
"api/current.txt",
diff --git a/test-runner/Android.bp b/test-runner/Android.bp
index 13a5dac9eb38..21e09d3221ce 100644
--- a/test-runner/Android.bp
+++ b/test-runner/Android.bp
@@ -79,12 +79,13 @@ java_library {
],
}
-// Make the current.txt available for use by the cts/tests/signature tests.
+// Make the current.txt available for use by the cts/tests/signature and /vendor tests.
// ========================================================================
filegroup {
name: "android-test-runner-current.txt",
visibility: [
"//cts/tests/signature/api",
+ "//vendor:__subpackages__",
],
srcs: [
"api/current.txt",
diff --git a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
index b0ca4d230e12..79d3a10a34cb 100644
--- a/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
+++ b/tests/FlickerTests/Rotation/src/com/android/server/wm/flicker/rotation/RotationTransition.kt
@@ -17,7 +17,10 @@
package com.android.server.wm.flicker.rotation
import android.platform.test.annotations.Presubmit
+import android.tools.common.flicker.subject.layers.LayerTraceEntrySubject
import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.common.traces.component.IComponentMatcher
+import android.tools.common.traces.surfaceflinger.Display
import android.tools.device.apphelpers.StandardAppHelper
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.LegacyFlickerTest
@@ -57,9 +60,8 @@ abstract class RotationTransition(flicker: LegacyFlickerTest) : BaseTest(flicker
@Test
open fun appLayerRotates_StartingPos() {
flicker.assertLayersStart {
- this.entry.displays.map { display ->
- this.visibleRegion(testApp).coversExactly(display.layerStackSpace)
- }
+ val display = getDisplay(testApp)
+ this.visibleRegion(testApp).coversAtLeast(display.layerStackSpace)
}
}
@@ -68,12 +70,20 @@ abstract class RotationTransition(flicker: LegacyFlickerTest) : BaseTest(flicker
@Test
open fun appLayerRotates_EndingPos() {
flicker.assertLayersEnd {
- this.entry.displays.map { display ->
- this.visibleRegion(testApp).coversExactly(display.layerStackSpace)
- }
+ val display = getDisplay(testApp)
+ this.visibleRegion(testApp).coversAtLeast(display.layerStackSpace)
}
}
+ private fun LayerTraceEntrySubject.getDisplay(componentMatcher: IComponentMatcher): Display {
+ val stackId = this.layer {
+ componentMatcher.layerMatchesAnyOf(it) && it.isVisible
+ }?.layer?.stackId ?: -1
+
+ return this.entry.displays.firstOrNull { it.layerStackId == stackId }
+ ?: error("Unable to find visible layer for $componentMatcher")
+ }
+
override fun cujCompleted() {
super.cujCompleted()
appLayerRotates_StartingPos()
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
index e3988cd20199..c44d9435d203 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingSecondaryActivity.java
@@ -68,7 +68,7 @@ public class ActivityEmbeddingSecondaryActivity extends Activity {
// This triggers a recalcuation of splitatributes.
ActivityEmbeddingController
.getInstance(ActivityEmbeddingSecondaryActivity.this)
- .invalidateTopVisibleActivityStacks();
+ .invalidateVisibleActivityStacks();
}
});
findViewById(R.id.secondary_enter_pip_button).setOnClickListener(
diff --git a/tests/testables/src/android/testing/TestableContext.java b/tests/testables/src/android/testing/TestableContext.java
index 0f04d6ae1721..fa8fdfac6813 100644
--- a/tests/testables/src/android/testing/TestableContext.java
+++ b/tests/testables/src/android/testing/TestableContext.java
@@ -62,8 +62,9 @@ import java.util.ArrayList;
*/
public class TestableContext extends ContextWrapper implements TestRule {
- private final TestableContentResolver mTestableContentResolver;
- private final TestableSettingsProvider mSettingsProvider;
+ private TestableContentResolver mTestableContentResolver;
+ private TestableSettingsProvider mSettingsProvider;
+ private RuntimeException mSettingsProviderFailure;
private ArrayList<MockServiceResolver> mMockServiceResolvers;
private ArrayMap<String, Object> mMockSystemServices;
@@ -83,12 +84,24 @@ public class TestableContext extends ContextWrapper implements TestRule {
public TestableContext(Context base, LeakCheck check) {
super(base);
- mTestableContentResolver = new TestableContentResolver(base);
- ContentProviderClient settings = base.getContentResolver()
- .acquireContentProviderClient(Settings.AUTHORITY);
- mSettingsProvider = TestableSettingsProvider.getFakeSettingsProvider(settings);
- mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
- mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+
+ // Configure TestableSettingsProvider when possible; if we fail to initialize some
+ // underlying infrastructure then remember the error and report it later when a test
+ // attempts to interact with it
+ try {
+ ContentProviderClient settings = base.getContentResolver()
+ .acquireContentProviderClient(Settings.AUTHORITY);
+ mSettingsProvider = TestableSettingsProvider.getFakeSettingsProvider(settings);
+ mTestableContentResolver = new TestableContentResolver(base);
+ mTestableContentResolver.addProvider(Settings.AUTHORITY, mSettingsProvider);
+ mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+ mSettingsProviderFailure = null;
+ } catch (Throwable t) {
+ mTestableContentResolver = null;
+ mSettingsProvider = null;
+ mSettingsProviderFailure = new RuntimeException(
+ "Failed to initialize TestableSettingsProvider", t);
+ }
mReceiver = check != null ? check.getTracker("receiver") : null;
mService = check != null ? check.getTracker("service") : null;
mComponent = check != null ? check.getTracker("component") : null;
@@ -171,11 +184,17 @@ public class TestableContext extends ContextWrapper implements TestRule {
}
TestableSettingsProvider getSettingsProvider() {
+ if (mSettingsProviderFailure != null) {
+ throw mSettingsProviderFailure;
+ }
return mSettingsProvider;
}
@Override
public TestableContentResolver getContentResolver() {
+ if (mSettingsProviderFailure != null) {
+ throw mSettingsProviderFailure;
+ }
return mTestableContentResolver;
}
@@ -515,12 +534,16 @@ public class TestableContext extends ContextWrapper implements TestRule {
return new TestWatcher() {
@Override
protected void succeeded(Description description) {
- mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+ if (mSettingsProvider != null) {
+ mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+ }
}
@Override
protected void failed(Throwable e, Description description) {
- mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+ if (mSettingsProvider != null) {
+ mSettingsProvider.clearValuesAndCheck(TestableContext.this);
+ }
}
}.apply(base, description);
}
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index 82e40b1eee6b..60c25b75d2e9 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -32,6 +32,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.Map;
+import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -76,7 +77,7 @@ public class TestableLooper {
}
private TestableLooper(TestLooperManager wrapper, Looper l) {
- mQueueWrapper = wrapper;
+ mQueueWrapper = Objects.requireNonNull(wrapper);
setupQueue(l);
}
@@ -282,65 +283,94 @@ public class TestableLooper {
return InstrumentationRegistry.getInstrumentation().acquireLooperManager(l);
}
- private static final Map<Object, TestableLooper> sLoopers = new ArrayMap<>();
+ private static final Map<Object, TestableLooperHolder> sLoopers = new ArrayMap<>();
/**
* For use with {@link RunWithLooper}, used to get the TestableLooper that was
* automatically created for this test.
*/
public static TestableLooper get(Object test) {
- return sLoopers.get(test);
+ final TestableLooperHolder looperHolder = sLoopers.get(test);
+ return (looperHolder != null) ? looperHolder.mTestableLooper : null;
}
public static void remove(Object test) {
sLoopers.remove(test);
}
- static class LooperFrameworkMethod extends FrameworkMethod {
+ /**
+ * Holder object that contains {@link TestableLooper} so that its initialization can be
+ * deferred until a test case is actually run, instead of forcing it to be created at
+ * {@link FrameworkMethod} construction time.
+ *
+ * This deferral is important because some test environments may configure
+ * {@link Looper#getMainLooper()} as part of a {@code Rule} instead of assuming it's globally
+ * initialized and unconditionally available.
+ */
+ private static class TestableLooperHolder {
+ private final boolean mSetAsMain;
+ private final Object mTest;
+
+ private TestableLooper mTestableLooper;
+ private Looper mLooper;
+ private Handler mHandler;
private HandlerThread mHandlerThread;
- private final TestableLooper mTestableLooper;
- private final Looper mLooper;
- private final Handler mHandler;
+ public TestableLooperHolder(boolean setAsMain, Object test) {
+ mSetAsMain = setAsMain;
+ mTest = test;
+ }
- public LooperFrameworkMethod(FrameworkMethod base, boolean setAsMain, Object test) {
- super(base.getMethod());
+ public void ensureInit() {
+ if (mLooper != null) return;
try {
- mLooper = setAsMain ? Looper.getMainLooper() : createLooper();
+ mLooper = mSetAsMain ? Looper.getMainLooper() : createLooper();
mTestableLooper = new TestableLooper(mLooper, false);
- if (!setAsMain) {
- mTestableLooper.getLooper().getThread().setName(test.getClass().getName());
+ if (!mSetAsMain) {
+ mTestableLooper.getLooper().getThread().setName(mTest.getClass().getName());
}
} catch (Exception e) {
throw new RuntimeException(e);
}
- sLoopers.put(test, mTestableLooper);
mHandler = new Handler(mLooper);
}
- public LooperFrameworkMethod(TestableLooper other, FrameworkMethod base) {
+ private Looper createLooper() {
+ // TODO: Find way to share these.
+ mHandlerThread = new HandlerThread(TestableLooper.class.getSimpleName());
+ mHandlerThread.start();
+ return mHandlerThread.getLooper();
+ }
+ }
+
+ static class LooperFrameworkMethod extends FrameworkMethod {
+ private TestableLooperHolder mLooperHolder;
+
+ public LooperFrameworkMethod(FrameworkMethod base, TestableLooperHolder looperHolder) {
super(base.getMethod());
- mLooper = other.mLooper;
- mTestableLooper = other;
- mHandler = Handler.createAsync(mLooper);
+ mLooperHolder = looperHolder;
}
public static FrameworkMethod get(FrameworkMethod base, boolean setAsMain, Object test) {
- if (sLoopers.containsKey(test)) {
- return new LooperFrameworkMethod(sLoopers.get(test), base);
+ TestableLooperHolder looperHolder = sLoopers.get(test);
+ if (looperHolder == null) {
+ looperHolder = new TestableLooperHolder(setAsMain, test);
+ sLoopers.put(test, looperHolder);
}
- return new LooperFrameworkMethod(base, setAsMain, test);
+ return new LooperFrameworkMethod(base, looperHolder);
}
@Override
public Object invokeExplosively(Object target, Object... params) throws Throwable {
- if (Looper.myLooper() == mLooper) {
+ mLooperHolder.ensureInit();
+ if (Looper.myLooper() == mLooperHolder.mLooper) {
// Already on the right thread from another statement, just execute then.
return super.invokeExplosively(target, params);
}
- boolean set = mTestableLooper.mQueueWrapper == null;
+ boolean set = mLooperHolder.mTestableLooper.mQueueWrapper == null;
if (set) {
- mTestableLooper.mQueueWrapper = acquireLooperManager(mLooper);
+ mLooperHolder.mTestableLooper.mQueueWrapper = acquireLooperManager(
+ mLooperHolder.mLooper);
}
try {
Object[] ret = new Object[1];
@@ -352,11 +382,11 @@ public class TestableLooper {
throw new LooperException(throwable);
}
};
- Message m = Message.obtain(mHandler, execute);
+ Message m = Message.obtain(mLooperHolder.mHandler, execute);
// Dispatch our message.
try {
- mTestableLooper.mQueueWrapper.execute(m);
+ mLooperHolder.mTestableLooper.mQueueWrapper.execute(m);
} catch (LooperException e) {
throw e.getSource();
} catch (RuntimeException re) {
@@ -373,27 +403,20 @@ public class TestableLooper {
return ret[0];
} finally {
if (set) {
- mTestableLooper.mQueueWrapper.release();
- mTestableLooper.mQueueWrapper = null;
- if (HOLD_MAIN_THREAD && mLooper == Looper.getMainLooper()) {
+ mLooperHolder.mTestableLooper.mQueueWrapper.release();
+ mLooperHolder.mTestableLooper.mQueueWrapper = null;
+ if (HOLD_MAIN_THREAD && mLooperHolder.mLooper == Looper.getMainLooper()) {
TestableInstrumentation.releaseMain();
}
}
}
}
- private Looper createLooper() {
- // TODO: Find way to share these.
- mHandlerThread = new HandlerThread(TestableLooper.class.getSimpleName());
- mHandlerThread.start();
- return mHandlerThread.getLooper();
- }
-
@Override
protected void finalize() throws Throwable {
super.finalize();
- if (mHandlerThread != null) {
- mHandlerThread.quit();
+ if (mLooperHolder.mHandlerThread != null) {
+ mLooperHolder.mHandlerThread.quit();
}
}
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index f05611048caa..1a82021bce71 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -381,9 +381,9 @@ class PackageFlattener {
return true;
}
- bool FlattenTypeSpec(const ResourceTableTypeView& type,
- const std::vector<ResourceTableEntryView>& sorted_entries,
- BigBuffer* buffer) {
+ ResTable_typeSpec* FlattenTypeSpec(const ResourceTableTypeView& type,
+ const std::vector<ResourceTableEntryView>& sorted_entries,
+ BigBuffer* buffer) {
ChunkWriter type_spec_writer(buffer);
ResTable_typeSpec* spec_header =
type_spec_writer.StartChunk<ResTable_typeSpec>(RES_TABLE_TYPE_SPEC_TYPE);
@@ -391,7 +391,7 @@ class PackageFlattener {
if (sorted_entries.empty()) {
type_spec_writer.Finish();
- return true;
+ return spec_header;
}
// We can't just take the size of the vector. There may be holes in the
@@ -427,7 +427,7 @@ class PackageFlattener {
}
}
type_spec_writer.Finish();
- return true;
+ return spec_header;
}
bool FlattenTypes(BigBuffer* buffer) {
@@ -450,7 +450,8 @@ class PackageFlattener {
expected_type_id++;
type_pool_.MakeRef(type.named_type.to_string());
- if (!FlattenTypeSpec(type, type.entries, buffer)) {
+ const auto type_spec_header = FlattenTypeSpec(type, type.entries, buffer);
+ if (!type_spec_header) {
return false;
}
@@ -511,6 +512,10 @@ class PackageFlattener {
return false;
}
}
+
+ // And now we can update the type entries count in the typeSpec header.
+ type_spec_header->typesCount = android::util::HostToDevice16(uint16_t(std::min<uint32_t>(
+ config_to_entry_list_map.size(), std::numeric_limits<uint16_t>::max())));
}
return true;
}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java
new file mode 100644
index 000000000000..379c4ae8a059
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/com/android/okhttp/internalandroidapi/Dns.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.okhttp.internalandroidapi;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+
+/**
+ * A domain name service that resolves IP addresses for host names.
+ * @hide
+ * @hide This class is not part of the Android public SDK API
+ */
+public interface Dns {
+ /**
+ * Returns the IP addresses of {@code hostname}, in the order they should
+ * be attempted.
+ *
+ * @hide
+ */
+ List<InetAddress> lookup(String hostname) throws UnknownHostException;
+}