summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AconfigFlags.bp5
-rw-r--r--Android.bp1
-rw-r--r--core/api/current.txt5
-rw-r--r--core/java/android/app/INotificationManager.aidl2
-rw-r--r--core/java/android/app/Notification.java59
-rw-r--r--core/java/android/hardware/face/FaceSensorConfigurations.java47
-rw-r--r--core/java/android/provider/ContactsContract.java91
-rw-r--r--core/java/android/service/notification/ZenModeConfig.java24
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java6
-rw-r--r--core/java/android/view/ImeInsetsSourceConsumer.java9
-rw-r--r--core/java/android/view/InsetsAnimationControlCallbacks.java7
-rw-r--r--core/java/android/view/InsetsAnimationControlImpl.java10
-rw-r--r--core/java/android/view/InsetsAnimationControlRunner.java28
-rw-r--r--core/java/android/view/InsetsAnimationThreadControlRunner.java42
-rw-r--r--core/java/android/view/InsetsController.java21
-rw-r--r--core/java/android/view/InsetsResizeAnimationRunner.java5
-rw-r--r--core/java/android/view/InsetsSourceConsumer.java71
-rw-r--r--core/java/android/window/flags/windowing_frontend.aconfig11
-rw-r--r--core/java/com/android/internal/os/anr/OWNERS1
-rw-r--r--core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java18
-rw-r--r--core/java/com/android/internal/protolog/ProtoLog.java8
-rw-r--r--core/java/com/android/internal/protolog/ProtoLogImpl.java30
-rw-r--r--core/tests/coretests/src/android/app/NotificationTest.java281
-rw-r--r--core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java13
-rw-r--r--core/tests/coretests/src/android/view/InsetsControllerTest.java7
-rw-r--r--core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java39
-rw-r--r--graphics/java/android/graphics/Bitmap.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java138
-rw-r--r--libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt1
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt8
-rw-r--r--libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java13
-rw-r--r--media/java/android/media/flags/editing.aconfig7
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml28
-rw-r--r--packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml39
-rw-r--r--packages/SettingsLib/Spa/build.gradle.kts2
-rw-r--r--packages/SettingsLib/Spa/gallery/res/values/strings.xml2
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt6
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt78
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt3
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt83
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt92
-rw-r--r--packages/SettingsLib/Spa/spa/build.gradle.kts5
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt5
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt4
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SystemProperties.kt35
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt145
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TopIntroPreference.kt152
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt132
-rw-r--r--packages/SettingsLib/Spa/tests/res/values/strings.xml2
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SystemPropertiesTest.kt30
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt51
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TopIntroPreferenceTest.kt75
-rw-r--r--packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ZeroStatePreferenceTest.kt56
-rw-r--r--packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml44
-rw-r--r--packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java5
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java6
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java (renamed from packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchTest.java (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSliceViewTest.java (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/NumPadAnimatorTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/PinShapeHintingViewTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/TestScopeProvider.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/TestScopeProvider.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt)0
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt7
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt2
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt26
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt75
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractorTest.kt100
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractorTest.kt139
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt40
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt4
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt45
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt16
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java (renamed from packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt (renamed from packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt)0
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt21
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt1
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModelKosmos.kt27
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStateCallbackInteractorKosmos.kt44
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt2
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java53
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java7
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java36
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java12
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java10
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java27
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java75
-rw-r--r--services/core/java/com/android/server/biometrics/AuthService.java4
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/FaceService.java24
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java53
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java79
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java4
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java15
-rw-r--r--services/core/java/com/android/server/display/DisplayTopologyCoordinator.java104
-rw-r--r--services/core/java/com/android/server/display/feature/DisplayManagerFlags.java9
-rw-r--r--services/core/java/com/android/server/display/feature/display_flags.aconfig8
-rw-r--r--services/core/java/com/android/server/input/debug/TouchpadDebugView.java39
-rw-r--r--services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java16
-rw-r--r--services/core/java/com/android/server/input/debug/TouchpadSelectionView.java111
-rw-r--r--services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java13
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java113
-rw-r--r--services/core/java/com/android/server/notification/PreferencesHelper.java40
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java16
-rw-r--r--services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java8
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java18
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java78
-rw-r--r--services/core/java/com/android/server/wm/Session.java6
-rw-r--r--services/core/java/com/android/server/wm/StartingData.java2
-rw-r--r--services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java9
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java3
-rw-r--r--services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt11
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java326
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java42
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java91
-rw-r--r--telephony/common/android/telephony/LocationAccessPolicy.java22
-rw-r--r--tests/FlickerTests/ActivityEmbedding/OWNERS1
-rw-r--r--tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java18
-rw-r--r--tools/systemfeatures/Android.bp4
-rw-r--r--tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt29
-rw-r--r--tools/systemfeatures/tests/golden/RoFeatures.java.gen8
-rw-r--r--tools/systemfeatures/tests/golden/RwFeatures.java.gen2
-rw-r--r--tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java24
174 files changed, 3938 insertions, 844 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index dd919cacc534..a0f38d9a7bd9 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -680,6 +680,11 @@ java_aconfig_library {
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+cc_aconfig_library {
+ name: "com.android.media.flags.editing-aconfig-cc",
+ aconfig_declarations: "com.android.media.flags.editing-aconfig",
+}
+
// MediaProjection
aconfig_declarations {
name: "com.android.media.flags.projection-aconfig",
diff --git a/Android.bp b/Android.bp
index 5b9f2cbf2d0d..8c0158549d2d 100644
--- a/Android.bp
+++ b/Android.bp
@@ -99,6 +99,7 @@ filegroup {
":android.hardware.biometrics.common-V4-java-source",
":android.hardware.biometrics.fingerprint-V5-java-source",
":android.hardware.biometrics.fingerprint.virtualhal-java-source",
+ ":android.hardware.biometrics.face.virtualhal-java-source",
":android.hardware.biometrics.face-V4-java-source",
":android.hardware.gnss-V2-java-source",
":android.hardware.graphics.common-V3-java-source",
diff --git a/core/api/current.txt b/core/api/current.txt
index d0e20031c74d..f817241d80da 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -6495,6 +6495,7 @@ package android.app {
field public static final int FLAG_NO_CLEAR = 32; // 0x20
field public static final int FLAG_ONGOING_EVENT = 2; // 0x2
field public static final int FLAG_ONLY_ALERT_ONCE = 8; // 0x8
+ field @FlaggedApi("android.app.api_rich_ongoing") public static final int FLAG_PROMOTED_ONGOING = 262144; // 0x40000
field @Deprecated public static final int FLAG_SHOW_LIGHTS = 1; // 0x1
field public static final int FOREGROUND_SERVICE_DEFAULT = 0; // 0x0
field public static final int FOREGROUND_SERVICE_DEFERRED = 2; // 0x2
@@ -36934,14 +36935,16 @@ package android.provider {
@FlaggedApi("android.provider.new_default_account_api_enabled") public static final class ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState {
ctor public ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState(int, @Nullable android.accounts.Account);
- method @Nullable public android.accounts.Account getCloudAccount();
+ method @Nullable public android.accounts.Account getAccount();
method public int getState();
method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofCloud(@NonNull android.accounts.Account);
method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofLocal();
method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofNotSet();
+ method @NonNull public static android.provider.ContactsContract.RawContacts.DefaultAccount.DefaultAccountAndState ofSim(@NonNull android.accounts.Account);
field public static final int DEFAULT_ACCOUNT_STATE_CLOUD = 3; // 0x3
field public static final int DEFAULT_ACCOUNT_STATE_LOCAL = 2; // 0x2
field public static final int DEFAULT_ACCOUNT_STATE_NOT_SET = 1; // 0x1
+ field public static final int DEFAULT_ACCOUNT_STATE_SIM = 4; // 0x4
}
public static final class ContactsContract.RawContacts.DisplayPhoto {
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index e2bee64cbb7b..b9fe356690e0 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -258,4 +258,6 @@ interface INotificationManager
@EnforcePermission(allOf={"INTERACT_ACROSS_USERS", "ACCESS_NOTIFICATIONS"})
void unregisterCallNotificationEventListener(String packageName, in UserHandle userHandle, in ICallNotificationEventCallback listener);
+ void setCanBePromoted(String pkg, int uid, boolean promote);
+ boolean canBePromoted(String pkg, int uid);
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 81d2c890ee31..4d73c354707d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -772,6 +772,17 @@ public class Notification implements Parcelable
@FlaggedApi(android.service.notification.Flags.FLAG_NOTIFICATION_SILENT_FLAG)
public static final int FLAG_SILENT = 1 << 17; //0x00020000
+ /**
+ * Bit to be bitwise-ored into the {@link #flags} field that should be
+ * set by the system if this notification is a promoted ongoing notification, either via a
+ * user setting or allowlist.
+ *
+ * Applications cannot set this flag directly, but the posting app and
+ * {@link android.service.notification.NotificationListenerService} can read it.
+ */
+ @FlaggedApi(Flags.FLAG_API_RICH_ONGOING)
+ public static final int FLAG_PROMOTED_ONGOING = 0x00040000;
+
private static final List<Class<? extends Style>> PLATFORM_STYLE_CLASSES = Arrays.asList(
BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class,
DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class,
@@ -3110,6 +3121,53 @@ public class Notification implements Parcelable
}
/**
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
+ public boolean containsCustomViews() {
+ return contentView != null
+ || bigContentView != null
+ || headsUpContentView != null
+ || (publicVersion != null
+ && (publicVersion.contentView != null
+ || publicVersion.bigContentView != null
+ || publicVersion.headsUpContentView != null));
+ }
+
+ /**
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
+ public boolean hasTitle() {
+ return extras != null
+ && (!TextUtils.isEmpty(extras.getCharSequence(EXTRA_TITLE))
+ || !TextUtils.isEmpty(extras.getCharSequence(EXTRA_TITLE_BIG)));
+ }
+
+ /**
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
+ public boolean hasPromotableStyle() {
+ //TODO(b/367739672): Add progress style
+ return extras == null || !extras.containsKey(Notification.EXTRA_TEMPLATE)
+ || isStyle(Notification.BigPictureStyle.class)
+ || isStyle(Notification.BigTextStyle.class)
+ || isStyle(Notification.CallStyle.class);
+ }
+
+ /**
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_UI_RICH_ONGOING)
+ public boolean hasPromotableCharacteristics() {
+ return isColorized()
+ && hasTitle()
+ && !containsCustomViews()
+ && hasPromotableStyle();
+ }
+
+ /**
* Whether this notification was posted by a headless system app.
*
* If we don't have enough information to figure this out, this will return false. Therefore,
@@ -7636,7 +7694,6 @@ public class Notification implements Parcelable
if (mLargeIcon != null || largeIcon != null) {
Resources resources = context.getResources();
- Class<? extends Style> style = getNotificationStyle();
int maxSize = resources.getDimensionPixelSize(isLowRam
? R.dimen.notification_right_icon_size_low_ram
: R.dimen.notification_right_icon_size);
diff --git a/core/java/android/hardware/face/FaceSensorConfigurations.java b/core/java/android/hardware/face/FaceSensorConfigurations.java
index 12471681f913..51c5f4c398a1 100644
--- a/core/java/android/hardware/face/FaceSensorConfigurations.java
+++ b/core/java/android/hardware/face/FaceSensorConfigurations.java
@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.SensorProps;
+import android.hardware.biometrics.face.virtualhal.IVirtualHal;
import android.os.Binder;
import android.os.Parcel;
import android.os.Parcelable;
@@ -160,6 +161,41 @@ public class FaceSensorConfigurations implements Parcelable {
dest.writeByte((byte) (mResetLockoutRequiresChallenge ? 1 : 0));
dest.writeMap(mSensorPropsMap);
}
+ /**
+ * Remap fqName of VHAL because the `virtual` instance is registered
+ * with IVirtulalHal now (IFace previously)
+ * @param fqName fqName to be translated
+ * @return real fqName
+ */
+ public static String remapFqName(String fqName) {
+ if (!fqName.contains(IFace.DESCRIPTOR + "/virtual")) {
+ return fqName; //no remap needed for real hardware HAL
+ } else {
+ //new Vhal instance name
+ return fqName.replace("IFace", "virtualhal.IVirtualHal");
+ }
+ }
+ /**
+ * @param fqName aidl interface instance name
+ * @return aidl interface
+ */
+ public static IFace getIFace(String fqName) {
+ if (fqName.contains("virtual")) {
+ String fqNameMapped = remapFqName(fqName);
+ Slog.i(TAG, "getIFace fqName is mapped: " + fqName + "->" + fqNameMapped);
+ try {
+ IVirtualHal vhal = IVirtualHal.Stub.asInterface(
+ Binder.allowBlocking(ServiceManager.waitForService(fqNameMapped)));
+ return vhal.getFaceHal();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception in vhal.getFaceHal() call" + fqNameMapped);
+ }
+ }
+
+ return IFace.Stub.asInterface(
+ Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
+ }
+
/**
* Returns face sensor props for the HAL {@param instance}.
@@ -173,14 +209,13 @@ public class FaceSensorConfigurations implements Parcelable {
return props;
}
- final String fqName = IFace.DESCRIPTOR + "/" + instance;
- IFace face = IFace.Stub.asInterface(Binder.allowBlocking(
- ServiceManager.waitForDeclaredService(fqName)));
try {
- if (face != null) {
- props = face.getSensorProps();
+ final String fqName = IFace.DESCRIPTOR + "/" + instance;
+ final IFace fp = getIFace(fqName);
+ if (fp != null) {
+ props = fp.getSensorProps();
} else {
- Slog.e(TAG, "Unable to get declared service: " + fqName);
+ Log.d(TAG, "IFace null for instance " + instance);
}
} catch (RemoteException e) {
Log.d(TAG, "Unable to get sensor properties!");
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index a62281049678..27b1dfbd9b18 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -3046,6 +3046,11 @@ public final class ContactsContract {
* <li> {@link #DEFAULT_ACCOUNT_STATE_CLOUD}: The default account is set to a
* cloud-synced account. New raw contacts requested for insertion without a specified
* {@link Account} will be saved in the default cloud account. </li>
+ * <li> {@link #DEFAULT_ACCOUNT_STATE_SIM}: The default account is set to a
+ * account that is associated with one of
+ * {@link SimContacts#getSimAccounts(ContentResolver)}. New raw contacts requested
+ * for insertion without a specified {@link Account} will be
+ * saved in this SIM account. </li>
* </ul>
*/
@FlaggedApi(Flags.FLAG_NEW_DEFAULT_ACCOUNT_API_ENABLED)
@@ -3063,44 +3068,51 @@ public final class ContactsContract {
public static final int DEFAULT_ACCOUNT_STATE_CLOUD = 3;
/**
+ * A state indicating that the default account is set as an account that is
+ * associated with one of {@link SimContacts#getSimAccounts(ContentResolver)}.
+ */
+ public static final int DEFAULT_ACCOUNT_STATE_SIM = 4;
+
+ /**
* The state of the default account. One of
* {@link #DEFAULT_ACCOUNT_STATE_NOT_SET},
- * {@link #DEFAULT_ACCOUNT_STATE_LOCAL} or
- * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}.
+ * {@link #DEFAULT_ACCOUNT_STATE_LOCAL},
+ * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}
+ * {@link #DEFAULT_ACCOUNT_STATE_SIM}.
*/
@DefaultAccountState
private final int mState;
/**
- * The account of the default account, when {@link mState} is {
+ * The account of the default account, when {@link #mState} is {
*
- * @link #STATE_SET_TO_CLOUD}, or null otherwise.
+ * @link #DEFAULT_ACCOUNT_STATE_CLOUD} or {@link #DEFAULT_ACCOUNT_STATE_SIM}, or
+ * null otherwise.
*/
- private final Account mCloudAccount;
+ private final Account mAccount;
/**
* Constructs a new `DefaultAccountAndState` instance with the specified state and
* cloud
* account.
*
- * @param state The state of the default account.
- * @param cloudAccount The cloud account associated with the default account,
- * or null if the state is not
- * {@link #DEFAULT_ACCOUNT_STATE_CLOUD}.
+ * @param state The state of the default account.
+ * @param account The account associated with the default account if the state is
+ * {@link #DEFAULT_ACCOUNT_STATE_CLOUD} or
+ * {@link #DEFAULT_ACCOUNT_STATE_SIM}, or null otherwise.
*/
public DefaultAccountAndState(@DefaultAccountState int state,
- @Nullable Account cloudAccount) {
+ @Nullable Account account) {
if (!isValidDefaultAccountState(state)) {
throw new IllegalArgumentException("Invalid default account state.");
}
- if ((state == DEFAULT_ACCOUNT_STATE_CLOUD) != (cloudAccount != null)) {
+ if (isCloudOrSimAccount(state) != (account != null)) {
throw new IllegalArgumentException(
- "Default account can be set to cloud if and only if the cloud "
+ "Default account can be set to cloud or SIM if and only if the "
+ "account is provided.");
}
this.mState = state;
- this.mCloudAccount =
- (mState == DEFAULT_ACCOUNT_STATE_CLOUD) ? cloudAccount : null;
+ this.mAccount = isCloudOrSimAccount(state) ? account : null;
}
/**
@@ -3118,6 +3130,21 @@ public final class ContactsContract {
return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_CLOUD, cloudAccount);
}
+
+ /**
+ * Creates a `DefaultAccountAndState` instance representing a default account
+ * that is set to the sim and associated with the specified sim account.
+ *
+ * @param simAccount The non-null sim account associated with the default
+ * contacts account.
+ * @return A new `DefaultAccountAndState` instance with state
+ * {@link #DEFAULT_ACCOUNT_STATE_SIM}.
+ */
+ public static @NonNull DefaultAccountAndState ofSim(
+ @NonNull Account simAccount) {
+ return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_SIM, simAccount);
+ }
+
/**
* Creates a `DefaultAccountAndState` instance representing a default account
* that is set to the local device storage.
@@ -3140,6 +3167,18 @@ public final class ContactsContract {
return new DefaultAccountAndState(DEFAULT_ACCOUNT_STATE_NOT_SET, null);
}
+ private static boolean isCloudOrSimAccount(@DefaultAccountState int state) {
+ return state == DEFAULT_ACCOUNT_STATE_CLOUD
+ || state == DEFAULT_ACCOUNT_STATE_SIM;
+ }
+
+ private static boolean isValidDefaultAccountState(int state) {
+ return state == DEFAULT_ACCOUNT_STATE_NOT_SET
+ || state == DEFAULT_ACCOUNT_STATE_LOCAL
+ || state == DEFAULT_ACCOUNT_STATE_CLOUD
+ || state == DEFAULT_ACCOUNT_STATE_SIM;
+ }
+
/**
* @return the state of the default account.
*/
@@ -3149,16 +3188,17 @@ public final class ContactsContract {
}
/**
- * @return the cloud account associated with the default account, or null if the
- * state is not {@link #DEFAULT_ACCOUNT_STATE_CLOUD}.
+ * @return the cloud account associated with the default account if the
+ * state is {@link #DEFAULT_ACCOUNT_STATE_CLOUD} or
+ * {@link #DEFAULT_ACCOUNT_STATE_SIM}.
*/
- public @Nullable Account getCloudAccount() {
- return mCloudAccount;
+ public @Nullable Account getAccount() {
+ return mAccount;
}
@Override
public int hashCode() {
- return Objects.hash(mState, mCloudAccount);
+ return Objects.hash(mState, mAccount);
}
@Override
@@ -3170,14 +3210,8 @@ public final class ContactsContract {
return false;
}
- return mState == that.mState && Objects.equals(mCloudAccount,
- that.mCloudAccount);
- }
-
- private static boolean isValidDefaultAccountState(int state) {
- return state == DEFAULT_ACCOUNT_STATE_NOT_SET
- || state == DEFAULT_ACCOUNT_STATE_LOCAL
- || state == DEFAULT_ACCOUNT_STATE_CLOUD;
+ return mState == that.mState && Objects.equals(mAccount,
+ that.mAccount);
}
/**
@@ -3189,7 +3223,8 @@ public final class ContactsContract {
@IntDef(
prefix = {"DEFAULT_ACCOUNT_STATE_"},
value = {DEFAULT_ACCOUNT_STATE_NOT_SET,
- DEFAULT_ACCOUNT_STATE_LOCAL, DEFAULT_ACCOUNT_STATE_CLOUD})
+ DEFAULT_ACCOUNT_STATE_LOCAL, DEFAULT_ACCOUNT_STATE_CLOUD,
+ DEFAULT_ACCOUNT_STATE_SIM})
public @interface DefaultAccountState {
}
}
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index d45b24ed69be..303197dfd82d 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -202,10 +202,8 @@ public class ZenModeConfig implements Parcelable {
private static final int DEFAULT_CALLS_SOURCE = SOURCE_STAR;
public static final String MANUAL_RULE_ID = "MANUAL_RULE";
- public static final String EVENTS_DEFAULT_RULE_ID = "EVENTS_DEFAULT_RULE";
+ public static final String EVENTS_OBSOLETE_RULE_ID = "EVENTS_DEFAULT_RULE";
public static final String EVERY_NIGHT_DEFAULT_RULE_ID = "EVERY_NIGHT_DEFAULT_RULE";
- public static final List<String> DEFAULT_RULE_IDS = Arrays.asList(EVERY_NIGHT_DEFAULT_RULE_ID,
- EVENTS_DEFAULT_RULE_ID);
public static final int[] ALL_DAYS = { Calendar.SUNDAY, Calendar.MONDAY, Calendar.TUESDAY,
Calendar.WEDNESDAY, Calendar.THURSDAY, Calendar.FRIDAY, Calendar.SATURDAY };
@@ -424,21 +422,10 @@ public class ZenModeConfig implements Parcelable {
return policy;
}
+ @FlaggedApi(Flags.FLAG_MODES_UI)
public static ZenModeConfig getDefaultConfig() {
ZenModeConfig config = new ZenModeConfig();
- EventInfo eventInfo = new EventInfo();
- eventInfo.reply = REPLY_YES_OR_MAYBE;
- ZenRule events = new ZenRule();
- events.id = EVENTS_DEFAULT_RULE_ID;
- events.conditionId = toEventConditionId(eventInfo);
- events.component = ComponentName.unflattenFromString(
- "android/com.android.server.notification.EventConditionProvider");
- events.enabled = false;
- events.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
- events.pkg = "android";
- config.automaticRules.put(EVENTS_DEFAULT_RULE_ID, events);
-
ScheduleInfo scheduleInfo = new ScheduleInfo();
scheduleInfo.days = new int[] {1, 2, 3, 4, 5, 6, 7};
scheduleInfo.startHour = 22;
@@ -457,6 +444,13 @@ public class ZenModeConfig implements Parcelable {
return config;
}
+ // TODO: b/368247671 - Can be made a constant again when modes_ui is inlined
+ public static List<String> getDefaultRuleIds() {
+ return Flags.modesUi()
+ ? List.of(EVERY_NIGHT_DEFAULT_RULE_ID)
+ : List.of(EVERY_NIGHT_DEFAULT_RULE_ID, EVENTS_OBSOLETE_RULE_ID);
+ }
+
void ensureManualZenRule() {
if (manualRule == null) {
final ZenRule newRule = new ZenRule();
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 384add5cf929..2ab16e91d987 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -2397,7 +2397,11 @@ public abstract class WallpaperService extends Service {
// it hasn't changed and there is no need to update.
ret = mBlastBufferQueue.createSurface();
} else {
- mBlastBufferQueue.update(mBbqSurfaceControl, width, height, format);
+ if (mBbqSurfaceControl != null && mBbqSurfaceControl.isValid()) {
+ mBlastBufferQueue.update(mBbqSurfaceControl, width, height, format);
+ } else {
+ Log.w(TAG, "Skipping BlastBufferQueue update - invalid surface control");
+ }
}
return ret;
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index e90b1c0fc167..229e8ee75844 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -26,7 +26,6 @@ import android.annotation.Nullable;
import android.os.IBinder;
import android.os.Trace;
import android.util.proto.ProtoOutputStream;
-import android.view.SurfaceControl.Transaction;
import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodManager;
@@ -34,8 +33,6 @@ import android.view.inputmethod.InputMethodManager;
import com.android.internal.inputmethod.ImeTracing;
import com.android.internal.inputmethod.SoftInputShowHideReason;
-import java.util.function.Supplier;
-
/**
* Controls the visibility and animations of IME window insets source.
* @hide
@@ -54,10 +51,8 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
*/
private boolean mIsRequestedVisibleAwaitingLeash;
- public ImeInsetsSourceConsumer(
- int id, InsetsState state, Supplier<Transaction> transactionSupplier,
- InsetsController controller) {
- super(id, WindowInsets.Type.ime(), state, transactionSupplier, controller);
+ public ImeInsetsSourceConsumer(int id, InsetsState state, InsetsController controller) {
+ super(id, WindowInsets.Type.ime(), state, controller);
}
@Override
diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java
index 04bb6091672b..a0d8a173c3e5 100644
--- a/core/java/android/view/InsetsAnimationControlCallbacks.java
+++ b/core/java/android/view/InsetsAnimationControlCallbacks.java
@@ -54,13 +54,6 @@ public interface InsetsAnimationControlCallbacks {
void notifyFinished(InsetsAnimationControlRunner runner, boolean shown);
/**
- * Apply the new params to the surface.
- * @param params The {@link android.view.SyncRtSurfaceTransactionApplier.SurfaceParams} to
- * apply.
- */
- void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params);
-
- /**
* Post a message to release the Surface, guaranteed to happen after all
* previous calls to applySurfaceParams.
*/
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 91e9230cdc6a..97facc1ba472 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -99,6 +99,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro
private final @InsetsType int mTypes;
private @InsetsType int mControllingTypes;
private final InsetsAnimationControlCallbacks mController;
+ private final SurfaceParamsApplier mSurfaceParamsApplier;
private final WindowInsetsAnimation mAnimation;
private final long mDurationMs;
private final Interpolator mInterpolator;
@@ -123,6 +124,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro
public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls,
@Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener,
@InsetsType int types, InsetsAnimationControlCallbacks controller,
+ SurfaceParamsApplier surfaceParamsApplier,
InsetsAnimationSpec insetsAnimationSpec, @AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
CompatibilityInfo.Translator translator, @Nullable ImeTracker.Token statsToken) {
@@ -131,6 +133,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro
mTypes = types;
mControllingTypes = types;
mController = controller;
+ mSurfaceParamsApplier = surfaceParamsApplier;
mInitialInsetsState = new InsetsState(state, true /* copySources */);
if (frame != null) {
final SparseIntArray idSideMap = new SparseIntArray();
@@ -258,6 +261,11 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro
}
@Override
+ public SurfaceParamsApplier getSurfaceParamsApplier() {
+ return mSurfaceParamsApplier;
+ }
+
+ @Override
@Nullable
public ImeTracker.Token getStatsToken() {
return mStatsToken;
@@ -305,7 +313,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro
updateLeashesForSide(SIDE_RIGHT, offset.right, params, outState, mPendingAlpha);
updateLeashesForSide(SIDE_BOTTOM, offset.bottom, params, outState, mPendingAlpha);
- mController.applySurfaceParams(params.toArray(new SurfaceParams[params.size()]));
+ mSurfaceParamsApplier.applySurfaceParams(params.toArray(new SurfaceParams[params.size()]));
mCurrentInsets = mPendingInsets;
mAnimation.setFraction(mPendingFraction);
mCurrentAlpha = mPendingAlpha;
diff --git a/core/java/android/view/InsetsAnimationControlRunner.java b/core/java/android/view/InsetsAnimationControlRunner.java
index 8cb8b47dd0ec..4f102da4692a 100644
--- a/core/java/android/view/InsetsAnimationControlRunner.java
+++ b/core/java/android/view/InsetsAnimationControlRunner.java
@@ -77,6 +77,11 @@ public interface InsetsAnimationControlRunner {
@AnimationType int getAnimationType();
/**
+ * @return The {@link SurfaceParamsApplier} this runner is using.
+ */
+ SurfaceParamsApplier getSurfaceParamsApplier();
+
+ /**
* @return The token tracking the current IME request or {@code null} otherwise.
*/
@Nullable
@@ -99,4 +104,27 @@ public interface InsetsAnimationControlRunner {
* @param fieldId FieldId of the implementation class
*/
void dumpDebug(ProtoOutputStream proto, long fieldId);
+
+ /**
+ * Interface applying given surface operations.
+ */
+ interface SurfaceParamsApplier {
+
+ SurfaceParamsApplier DEFAULT = params -> {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ for (int i = params.length - 1; i >= 0; i--) {
+ SyncRtSurfaceTransactionApplier.applyParams(t, params[i], new float[9]);
+ }
+ t.apply();
+ t.close();
+ };
+
+ /**
+ * Apply the new params to the surface.
+ *
+ * @param params The {@link SyncRtSurfaceTransactionApplier.SurfaceParams} to apply.
+ */
+ void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params);
+
+ }
}
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index fc185bc73735..8c2c4951a9f7 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -17,7 +17,6 @@
package android.view;
import static android.view.InsetsController.DEBUG;
-import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
import android.annotation.Nullable;
import android.annotation.UiThread;
@@ -30,7 +29,6 @@ import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsController.AnimationType;
import android.view.InsetsController.LayoutInsetsDuringAnimation;
-import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;
import android.view.inputmethod.ImeTracker;
@@ -50,8 +48,6 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro
private final InsetsAnimationControlCallbacks mCallbacks =
new InsetsAnimationControlCallbacks() {
- private final float[] mTmpFloat9 = new float[9];
-
@Override
@UiThread
public <T extends InsetsAnimationControlRunner & InternalInsetsAnimationController>
@@ -81,19 +77,6 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro
}
@Override
- public void applySurfaceParams(SurfaceParams... params) {
- if (DEBUG) Log.d(TAG, "applySurfaceParams");
- SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- for (int i = params.length - 1; i >= 0; i--) {
- SyncRtSurfaceTransactionApplier.SurfaceParams surfaceParams = params[i];
- applyParams(t, surfaceParams, mTmpFloat9);
- }
- t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
- t.apply();
- t.close();
- }
-
- @Override
public void releaseSurfaceControlFromRt(SurfaceControl sc) {
if (DEBUG) Log.d(TAG, "releaseSurfaceControlFromRt");
// Since we don't push the SurfaceParams to the RT we can release directly
@@ -106,6 +89,22 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro
}
};
+ private SurfaceParamsApplier mSurfaceParamsApplier = new SurfaceParamsApplier() {
+
+ private final float[] mTmpFloat9 = new float[9];
+
+ @Override
+ public void applySurfaceParams(SyncRtSurfaceTransactionApplier.SurfaceParams... params) {
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ for (int i = params.length - 1; i >= 0; i--) {
+ SyncRtSurfaceTransactionApplier.applyParams(t, params[i], mTmpFloat9);
+ }
+ t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
+ t.apply();
+ t.close();
+ }
+ };
+
@UiThread
public InsetsAnimationThreadControlRunner(SparseArray<InsetsSourceControl> controls,
@Nullable Rect frame, InsetsState state, WindowInsetsAnimationControlListener listener,
@@ -117,8 +116,8 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro
mMainThreadHandler = mainThreadHandler;
mOuterCallbacks = controller;
mControl = new InsetsAnimationControlImpl(controls, frame, state, listener, types,
- mCallbacks, insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
- translator, statsToken);
+ mCallbacks, mSurfaceParamsApplier, insetsAnimationSpec, animationType,
+ layoutInsetsDuringAnimation, translator, statsToken);
InsetsAnimationThread.getHandler().post(() -> {
if (mControl.isCancelled()) {
return;
@@ -187,6 +186,11 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro
}
@Override
+ public SurfaceParamsApplier getSurfaceParamsApplier() {
+ return mSurfaceParamsApplier;
+ }
+
+ @Override
public void updateLayoutInsetsDuringAnimation(
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) {
InsetsAnimationThread.getHandler().post(
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 8fdf91a2d87c..e38281ffd020 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -55,7 +55,6 @@ import android.util.Pair;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.InsetsSourceConsumer.ShowResult;
-import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimation.Bounds;
@@ -85,7 +84,8 @@ import java.util.Objects;
* Implements {@link WindowInsetsController} on the client.
* @hide
*/
-public class InsetsController implements WindowInsetsController, InsetsAnimationControlCallbacks {
+public class InsetsController implements WindowInsetsController, InsetsAnimationControlCallbacks,
+ InsetsAnimationControlRunner.SurfaceParamsApplier {
private int mTypesBeingCancelled;
@@ -307,7 +307,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
/** Not running an animation. */
- @VisibleForTesting
public static final int ANIMATION_TYPE_NONE = -1;
/** Running animation will show insets */
@@ -317,11 +316,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
public static final int ANIMATION_TYPE_HIDE = 1;
/** Running animation is controlled by user via {@link #controlWindowInsetsAnimation} */
- @VisibleForTesting(visibility = PACKAGE)
public static final int ANIMATION_TYPE_USER = 2;
/** Running animation will resize insets */
- @VisibleForTesting
public static final int ANIMATION_TYPE_RESIZE = 3;
@Retention(RetentionPolicy.SOURCE)
@@ -757,11 +754,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
public InsetsController(Host host) {
this(host, (controller, id, type) -> {
if (!Flags.refactorInsetsController() && type == ime()) {
- return new ImeInsetsSourceConsumer(id, controller.mState,
- Transaction::new, controller);
+ return new ImeInsetsSourceConsumer(id, controller.mState, controller);
} else {
- return new InsetsSourceConsumer(id, type, controller.mState,
- Transaction::new, controller);
+ return new InsetsSourceConsumer(id, type, controller.mState, controller);
}
}, host.getHandler());
}
@@ -1525,9 +1520,15 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
insetsAnimationSpec, animationType, layoutInsetsDuringAnimation,
mHost.getTranslator(), mHost.getHandler(), statsToken)
: new InsetsAnimationControlImpl(controls,
- frame, mState, listener, typesReady, this, insetsAnimationSpec,
+ frame, mState, listener, typesReady, this, this, insetsAnimationSpec,
animationType, layoutInsetsDuringAnimation, mHost.getTranslator(),
statsToken);
+ for (int i = controls.size() - 1; i >= 0; i--) {
+ final InsetsSourceConsumer consumer = mSourceConsumers.get(controls.keyAt(i));
+ if (consumer != null) {
+ consumer.setSurfaceParamsApplier(runner.getSurfaceParamsApplier());
+ }
+ }
if ((typesReady & WindowInsets.Type.ime()) != 0) {
ImeTracing.getInstance().triggerClientDump("InsetsAnimationControlImpl",
mHost.getInputMethodManager(), null /* icProto */);
diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java
index f90b8411e333..5262751cc6ed 100644
--- a/core/java/android/view/InsetsResizeAnimationRunner.java
+++ b/core/java/android/view/InsetsResizeAnimationRunner.java
@@ -94,6 +94,11 @@ public class InsetsResizeAnimationRunner implements InsetsAnimationControlRunner
}
@Override
+ public SurfaceParamsApplier getSurfaceParamsApplier() {
+ return SurfaceParamsApplier.DEFAULT;
+ }
+
+ @Override
@Nullable
public ImeTracker.Token getStatsToken() {
// Return null as resizing the IME view is not explicitly tracked.
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 391d757365e6..2e2ff1d49dfe 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -17,6 +17,7 @@
package android.view;
import static android.view.InsetsController.ANIMATION_TYPE_NONE;
+import static android.view.InsetsController.ANIMATION_TYPE_RESIZE;
import static android.view.InsetsController.AnimationType;
import static android.view.InsetsController.DEBUG;
import static android.view.InsetsSourceConsumerProto.ANIMATION_STATE;
@@ -32,12 +33,13 @@ import static com.android.window.flags.Flags.insetsControlSeq;
import android.annotation.IntDef;
import android.annotation.Nullable;
+import android.graphics.Matrix;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.text.TextUtils;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
-import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type.InsetsType;
import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
@@ -48,7 +50,6 @@ import com.android.internal.inputmethod.ImeTracing;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
-import java.util.function.Supplier;
/**
* Controls the visibility and animations of a single window insets source.
@@ -92,10 +93,12 @@ public class InsetsSourceConsumer {
private final int mType;
private static final String TAG = "InsetsSourceConsumer";
- private final Supplier<Transaction> mTransactionSupplier;
@Nullable
private InsetsSourceControl mSourceControl;
private boolean mHasWindowFocus;
+ private InsetsAnimationControlRunner.SurfaceParamsApplier mSurfaceParamsApplier =
+ InsetsAnimationControlRunner.SurfaceParamsApplier.DEFAULT;
+ private final Matrix mTmpMatrix = new Matrix();
/**
* Whether the view has focus returned by {@link #onWindowFocusGained(boolean)}.
@@ -108,16 +111,13 @@ public class InsetsSourceConsumer {
* @param id The ID of the consumed insets.
* @param type The {@link InsetsType} of the consumed insets.
* @param state The current {@link InsetsState} of the consumed insets.
- * @param transactionSupplier The source of new {@link Transaction} instances. The supplier
- * must provide *new* instances, which will be explicitly closed by this class.
* @param controller The {@link InsetsController} to use for insets interaction.
*/
public InsetsSourceConsumer(int id, @InsetsType int type, InsetsState state,
- Supplier<Transaction> transactionSupplier, InsetsController controller) {
+ InsetsController controller) {
mId = id;
mType = type;
mState = state;
- mTransactionSupplier = transactionSupplier;
mController = controller;
}
@@ -162,6 +162,9 @@ public class InsetsSourceConsumer {
if (localVisible != serverVisible) {
mController.notifyVisibilityChanged();
}
+
+ // Reset the applier to the default one which has the most lightweight implementation.
+ setSurfaceParamsApplier(InsetsAnimationControlRunner.SurfaceParamsApplier.DEFAULT);
} else {
final boolean requestedVisible = isRequestedVisibleAwaitingControl();
final SurfaceControl oldLeash = lastControl != null ? lastControl.getLeash() : null;
@@ -184,10 +187,11 @@ public class InsetsSourceConsumer {
mController.notifyVisibilityChanged();
}
- // If we have a new leash, make sure visibility is up-to-date, even though we
- // didn't want to run an animation above.
- if (mController.getAnimationType(mType) == ANIMATION_TYPE_NONE) {
- applyRequestedVisibilityToControl();
+ // If there is no animation controlling the leash, make sure the visibility and the
+ // position is up-to-date. Note: ANIMATION_TYPE_RESIZE doesn't control the leash.
+ final int animType = mController.getAnimationType(mType);
+ if (animType == ANIMATION_TYPE_NONE || animType == ANIMATION_TYPE_RESIZE) {
+ applyRequestedVisibilityAndPositionToControl();
}
// Remove the surface that owned by last control when it lost.
@@ -229,6 +233,15 @@ public class InsetsSourceConsumer {
}
/**
+ * Sets the SurfaceParamsApplier that the latest animation runner is using. The leash owned by
+ * this class is always applied by the applier, so that the transaction order can always be
+ * aligned with the calling sequence.
+ */
+ void setSurfaceParamsApplier(InsetsAnimationControlRunner.SurfaceParamsApplier applier) {
+ mSurfaceParamsApplier = applier;
+ }
+
+ /**
* Called right after the animation is started or finished.
*/
@VisibleForTesting(visibility = PACKAGE)
@@ -431,24 +444,30 @@ public class InsetsSourceConsumer {
if (DEBUG) Log.d(TAG, "updateSource: " + newSource);
}
- private void applyRequestedVisibilityToControl() {
- if (mSourceControl == null || mSourceControl.getLeash() == null) {
+ private void applyRequestedVisibilityAndPositionToControl() {
+ if (mSourceControl == null) {
return;
}
-
- final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0;
- try (Transaction t = mTransactionSupplier.get()) {
- if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + requestedVisible);
- if (requestedVisible) {
- t.show(mSourceControl.getLeash());
- } else {
- t.hide(mSourceControl.getLeash());
- }
- // Ensure the alpha value is aligned with the actual requested visibility.
- t.setAlpha(mSourceControl.getLeash(), requestedVisible ? 1 : 0);
- t.apply();
+ final SurfaceControl leash = mSourceControl.getLeash();
+ if (leash == null) {
+ return;
}
- onPerceptible(requestedVisible);
+
+ final boolean visible = (mController.getRequestedVisibleTypes() & mType) != 0;
+ final Point surfacePosition = mSourceControl.getSurfacePosition();
+
+ if (DEBUG) Log.d(TAG, "applyRequestedVisibilityAndPositionToControl: visible=" + visible
+ + " position=" + surfacePosition);
+
+ mTmpMatrix.setTranslate(surfacePosition.x, surfacePosition.y);
+ mSurfaceParamsApplier.applySurfaceParams(
+ new SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(leash)
+ .withVisibility(visible)
+ .withAlpha(visible ? 1 : 0)
+ .withMatrix(mTmpMatrix)
+ .build());
+
+ onPerceptible(visible);
}
void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index e1402f8224eb..b6aad1145880 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -266,3 +266,14 @@ flag {
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "remove_starting_window_wait_for_multi_transitions"
+ namespace: "windowing_frontend"
+ description: "Avoid remove starting window too early when playing multiple transitions"
+ bug: "362347290"
+ is_fixed_read_only: true
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/com/android/internal/os/anr/OWNERS b/core/java/com/android/internal/os/anr/OWNERS
index 9816752db891..1ad642f78cde 100644
--- a/core/java/com/android/internal/os/anr/OWNERS
+++ b/core/java/com/android/internal/os/anr/OWNERS
@@ -1,3 +1,2 @@
benmiles@google.com
-gaillard@google.com
mohamadmahmoud@google.com \ No newline at end of file
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index fbc058cc0330..b0e38e256430 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -122,18 +122,20 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto
private final Lock mBackgroundServiceLock = new ReentrantLock();
private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
- public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups) {
+ public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups)
+ throws ServiceManager.ServiceNotFoundException {
this(null, null, null, () -> {}, groups);
}
- public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups) {
+ public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups)
+ throws ServiceManager.ServiceNotFoundException {
this(null, null, null, cacheUpdater, groups);
}
public PerfettoProtoLogImpl(
@NonNull String viewerConfigFilePath,
@NonNull Runnable cacheUpdater,
- @NonNull IProtoLogGroup[] groups) {
+ @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
this(viewerConfigFilePath,
null,
new ProtoLogViewerConfigReader(() -> {
@@ -177,12 +179,14 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto
@Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
@Nullable ProtoLogViewerConfigReader viewerConfigReader,
@NonNull Runnable cacheUpdater,
- @NonNull IProtoLogGroup[] groups) {
+ @NonNull IProtoLogGroup[] groups) throws ServiceManager.ServiceNotFoundException {
this(viewerConfigFilePath, viewerConfigInputStreamProvider, viewerConfigReader,
cacheUpdater, groups,
ProtoLogDataSource::new,
- IProtoLogConfigurationService.Stub
- .asInterface(ServiceManager.getService(PROTOLOG_CONFIGURATION_SERVICE))
+ android.tracing.Flags.clientSideProtoLogging() ?
+ IProtoLogConfigurationService.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(PROTOLOG_CONFIGURATION_SERVICE)
+ ) : null
);
}
@@ -222,7 +226,7 @@ public class PerfettoProtoLogImpl extends IProtoLogClient.Stub implements IProto
if (android.tracing.Flags.clientSideProtoLogging()) {
mProtoLogConfigurationService = configurationService;
Objects.requireNonNull(mProtoLogConfigurationService,
- "ServiceManager returned a null ProtoLog Configuration Service");
+ "A null ProtoLog Configuration Service was provided!");
try {
var args = new ProtoLogConfigurationServiceImpl.RegisterClientArgs();
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index bf77db7b6a33..adf03fe5f775 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -16,6 +16,8 @@
package com.android.internal.protolog;
+import android.os.ServiceManager;
+
import com.android.internal.protolog.common.IProtoLog;
import com.android.internal.protolog.common.IProtoLogGroup;
import com.android.internal.protolog.common.LogLevel;
@@ -76,7 +78,11 @@ public class ProtoLog {
groups = allGroups.toArray(new IProtoLogGroup[0]);
}
- sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+ try {
+ sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ throw new RuntimeException(e);
+ }
}
} else {
sProtoLogInstance = new LogcatOnlyProtoLogImpl();
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 7bdcf2d14b19..5d67534b1b44 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -23,6 +23,7 @@ import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.LO
import static com.android.internal.protolog.common.ProtoLogToolInjected.Value.VIEWER_CONFIG_PATH;
import android.annotation.Nullable;
+import android.os.ServiceManager;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -106,18 +107,23 @@ public class ProtoLogImpl {
final var groups = sLogGroups.values().toArray(new IProtoLogGroup[0]);
if (android.tracing.Flags.perfettoProtologTracing()) {
- File f = new File(sViewerConfigPath);
- if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) {
- // TODO(b/353530422): Remove - temporary fix to unblock b/352290057
- // In some tests the viewer config file might not exist in which we don't
- // want to provide config path to the user
- Log.w(LOG_TAG, "Failed to find viewerConfigFile when setting up "
- + ProtoLogImpl.class.getSimpleName() + ". "
- + "Setting up without a viewer config instead...");
- sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups);
- } else {
- sServiceInstance =
- new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups);
+ try {
+ File f = new File(sViewerConfigPath);
+ if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) {
+ // TODO(b/353530422): Remove - temporary fix to unblock b/352290057
+ // In some tests the viewer config file might not exist in which we don't
+ // want to provide config path to the user
+ Log.w(LOG_TAG, "Failed to find viewerConfigFile when setting up "
+ + ProtoLogImpl.class.getSimpleName() + ". "
+ + "Setting up without a viewer config instead...");
+
+ sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups);
+ } else {
+ sServiceInstance =
+ new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups);
+ }
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ throw new RuntimeException(e);
}
} else {
var protologImpl = new LegacyProtoLogImpl(
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index 0837b458c3ba..0f73df92ca93 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -37,6 +37,7 @@ import static android.app.Notification.EXTRA_PICTURE;
import static android.app.Notification.EXTRA_PICTURE_ICON;
import static android.app.Notification.EXTRA_SUMMARY_TEXT;
import static android.app.Notification.EXTRA_TITLE;
+import static android.app.Notification.FLAG_CAN_COLORIZE;
import static android.app.Notification.GROUP_ALERT_CHILDREN;
import static android.app.Notification.GROUP_ALERT_SUMMARY;
import static android.app.Notification.GROUP_KEY_SILENT;
@@ -96,6 +97,7 @@ import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.text.style.TextAppearanceSpan;
import android.util.Pair;
+import android.util.Slog;
import android.widget.RemoteViews;
import androidx.test.InstrumentationRegistry;
@@ -126,6 +128,8 @@ public class NotificationTest {
private Context mContext;
+ private RemoteViews mRemoteViews;
+
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -133,23 +137,25 @@ public class NotificationTest {
@Before
public void setUp() {
mContext = InstrumentationRegistry.getContext();
+ mRemoteViews = new RemoteViews(
+ mContext.getPackageName(), R.layout.notification_template_header);
}
@Test
public void testColorizedByPermission() {
Notification n = new Notification.Builder(mContext, "test")
- .setFlag(Notification.FLAG_CAN_COLORIZE, true)
+ .setFlag(FLAG_CAN_COLORIZE, true)
.setColorized(true).setColor(Color.WHITE)
.build();
assertTrue(n.isColorized());
n = new Notification.Builder(mContext, "test")
- .setFlag(Notification.FLAG_CAN_COLORIZE, true)
+ .setFlag(FLAG_CAN_COLORIZE, true)
.build();
assertFalse(n.isColorized());
n = new Notification.Builder(mContext, "test")
- .setFlag(Notification.FLAG_CAN_COLORIZE, false)
+ .setFlag(FLAG_CAN_COLORIZE, false)
.setColorized(true).setColor(Color.WHITE)
.build();
assertFalse(n.isColorized());
@@ -215,6 +221,275 @@ public class NotificationTest {
}
@Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasTitle_noStyle() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setContentTitle("TITLE")
+ .build();
+ assertThat(n.hasTitle()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasTitle_bigText() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .build();
+ assertThat(n.hasTitle()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasTitle_noTitle() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setContentText("text not title")
+ .build();
+ assertThat(n.hasTitle()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testContainsCustomViews_none() {
+ Notification np = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .build();
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setPublicVersion(np)
+ .build();
+ assertThat(n.containsCustomViews()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testContainsCustomViews_content() {
+ Notification np = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .build();
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setCustomContentView(mRemoteViews)
+ .setPublicVersion(np)
+ .build();
+ assertThat(n.containsCustomViews()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testContainsCustomViews_big() {
+ Notification np = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .build();
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setCustomBigContentView(mRemoteViews)
+ .setPublicVersion(np)
+ .build();
+ assertThat(n.containsCustomViews()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testContainsCustomViews_headsUp() {
+ Notification np = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .build();
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setCustomHeadsUpContentView(mRemoteViews)
+ .setPublicVersion(np)
+ .build();
+ assertThat(n.containsCustomViews()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testContainsCustomViews_content_public() {
+ Notification np = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("public")
+ .setCustomContentView(mRemoteViews)
+ .build();
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setPublicVersion(np)
+ .build();
+ assertThat(n.containsCustomViews()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testContainsCustomViews_big_public() {
+ Notification np = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setCustomBigContentView(mRemoteViews)
+ .build();
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setPublicVersion(np)
+ .build();
+ assertThat(n.containsCustomViews()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testContainsCustomViews_headsUp_public() {
+ Notification np = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setCustomHeadsUpContentView(mRemoteViews)
+ .build();
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .setPublicVersion(np)
+ .build();
+ assertThat(n.containsCustomViews()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableStyle_noStyle() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setContentText("test")
+ .build();
+ assertThat(n.hasPromotableStyle()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableStyle_bigPicture() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigPictureStyle())
+ .build();
+ assertThat(n.hasPromotableStyle()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableStyle_bigText() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle())
+ .build();
+ assertThat(n.hasPromotableStyle()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableStyle_no_messagingStyle() {
+ Notification.MessagingStyle style = new Notification.MessagingStyle("self name")
+ .setGroupConversation(true)
+ .setConversationTitle("test conversation title");
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(style)
+ .build();
+ assertThat(n.hasPromotableStyle()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableStyle_no_mediaStyle() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.MediaStyle())
+ .build();
+ assertThat(n.hasPromotableStyle()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableStyle_no_inboxStyle() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.InboxStyle())
+ .build();
+ assertThat(n.hasPromotableStyle()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableStyle_callText() {
+ PendingIntent answerIntent = createPendingIntent("answer");
+ PendingIntent declineIntent = createPendingIntent("decline");
+ Notification.CallStyle style = Notification.CallStyle.forIncomingCall(
+ new Person.Builder().setName("A Caller").build(),
+ declineIntent,
+ answerIntent
+ );
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(style)
+ .build();
+ assertThat(n.hasPromotableStyle()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableCharacteristics() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_CAN_COLORIZE, true)
+ .build();
+ assertThat(n.hasPromotableCharacteristics()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableCharacteristics_wrongStyle() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.InboxStyle())
+ .setContentTitle("TITLE")
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_CAN_COLORIZE, true)
+ .build();
+ assertThat(n.hasPromotableCharacteristics()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableCharacteristics_notColorized() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .build();
+ assertThat(n.hasPromotableCharacteristics()).isFalse();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_UI_RICH_ONGOING)
+ public void testHasPromotableCharacteristics_noTitle() {
+ Notification n = new Notification.Builder(mContext, "test")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle())
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_CAN_COLORIZE, true)
+ .build();
+ assertThat(n.hasPromotableCharacteristics()).isFalse();
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_API_RICH_ONGOING)
public void testGetShortCriticalText_noneSet() {
Notification n = new Notification.Builder(mContext, "test")
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 786f1e84728d..ba6f62c6ed19 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -38,7 +38,6 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.platform.test.annotations.Presubmit;
import android.util.SparseArray;
-import android.view.SurfaceControl.Transaction;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
@@ -79,7 +78,6 @@ public class InsetsAnimationControlImplTest {
private SurfaceControl mNavLeash;
private InsetsState mInsetsState;
- @Mock Transaction mMockTransaction;
@Mock InsetsController mMockController;
@Mock WindowInsetsAnimationControlListener mMockListener;
@@ -98,16 +96,14 @@ public class InsetsAnimationControlImplTest {
mInsetsState.getOrCreateSource(ID_NAVIGATION_BAR, navigationBars())
.setFrame(new Rect(400, 0, 500, 500));
InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ID_STATUS_BAR,
- WindowInsets.Type.statusBars(), mInsetsState,
- () -> mMockTransaction, mMockController);
+ WindowInsets.Type.statusBars(), mInsetsState, mMockController);
topConsumer.setControl(
new InsetsSourceControl(ID_STATUS_BAR, WindowInsets.Type.statusBars(),
mStatusLeash, true, new Point(0, 0), Insets.of(0, 100, 0, 0)),
new int[1], new int[1]);
InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(ID_NAVIGATION_BAR,
- WindowInsets.Type.navigationBars(), mInsetsState,
- () -> mMockTransaction, mMockController);
+ WindowInsets.Type.navigationBars(), mInsetsState, mMockController);
navConsumer.setControl(
new InsetsSourceControl(ID_NAVIGATION_BAR, WindowInsets.Type.navigationBars(),
mNavLeash, true, new Point(400, 0), Insets.of(0, 0, 100, 0)),
@@ -131,8 +127,9 @@ public class InsetsAnimationControlImplTest {
mController = new InsetsAnimationControlImpl(controls,
new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
- mMockController, spec /* insetsAnimationSpecCreator */, 0 /* animationType */,
- 0 /* layoutInsetsDuringAnimation */, null /* translator */, null /* statsToken */);
+ mMockController, mMockController, spec /* insetsAnimationSpecCreator */,
+ 0 /* animationType */, 0 /* layoutInsetsDuringAnimation */, null /* translator */,
+ null /* statsToken */);
mController.setReadyDispatched(true);
}
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index bec8b1f76394..4516e9ce72fc 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -63,7 +63,6 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.os.CancellationSignal;
import android.platform.test.annotations.Presubmit;
-import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsController.OnControllableInsetsChangedListener;
import android.view.WindowManager.BadTokenException;
@@ -138,8 +137,7 @@ public class InsetsControllerTest {
mTestHost = spy(new TestHost(mViewRoot));
mController = new InsetsController(mTestHost, (controller, id, type) -> {
if (!Flags.refactorInsetsController() && type == ime()) {
- return new InsetsSourceConsumer(id, type, controller.getState(),
- Transaction::new, controller) {
+ return new InsetsSourceConsumer(id, type, controller.getState(), controller) {
private boolean mImeRequestedShow;
@@ -155,8 +153,7 @@ public class InsetsControllerTest {
}
};
} else {
- return new InsetsSourceConsumer(id, type, controller.getState(),
- Transaction::new, controller);
+ return new InsetsSourceConsumer(id, type, controller.getState(), controller);
}
}, mTestHandler);
final Rect rect = new Rect(5, 5, 5, 5);
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 655cb4519d3c..d6d45e839f2f 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -28,10 +28,7 @@ import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
import android.app.Instrumentation;
import android.content.Context;
@@ -39,7 +36,6 @@ import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
-import android.view.SurfaceControl.Transaction;
import android.view.WindowManager.BadTokenException;
import android.view.WindowManager.LayoutParams;
import android.view.inputmethod.ImeTracker;
@@ -51,7 +47,6 @@ import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@@ -75,9 +70,9 @@ public class InsetsSourceConsumerTest {
private SurfaceSession mSession = new SurfaceSession();
private SurfaceControl mLeash;
- @Mock Transaction mMockTransaction;
private InsetsSource mSpyInsetsSource;
private boolean mRemoveSurfaceCalled = false;
+ private boolean mSurfaceParamsApplied = false;
private InsetsController mController;
private InsetsState mState;
private ViewRootImpl mViewRoot;
@@ -102,9 +97,14 @@ public class InsetsSourceConsumerTest {
mSpyInsetsSource = Mockito.spy(new InsetsSource(ID_STATUS_BAR, statusBars()));
mState.addSource(mSpyInsetsSource);
- mController = new InsetsController(new ViewRootInsetsControllerHost(mViewRoot));
- mConsumer = new InsetsSourceConsumer(ID_STATUS_BAR, statusBars(), mState,
- () -> mMockTransaction, mController) {
+ mController = new InsetsController(new ViewRootInsetsControllerHost(mViewRoot)) {
+ @Override
+ public void applySurfaceParams(
+ final SyncRtSurfaceTransactionApplier.SurfaceParams... params) {
+ mSurfaceParamsApplied = true;
+ }
+ };
+ mConsumer = new InsetsSourceConsumer(ID_STATUS_BAR, statusBars(), mState, mController) {
@Override
public void removeSurface() {
super.removeSurface();
@@ -148,8 +148,7 @@ public class InsetsSourceConsumerTest {
InsetsState state = new InsetsState();
InsetsController controller = new InsetsController(new ViewRootInsetsControllerHost(
mViewRoot));
- InsetsSourceConsumer consumer = new InsetsSourceConsumer(
- ID_IME, ime(), state, null, controller);
+ InsetsSourceConsumer consumer = new InsetsSourceConsumer(ID_IME, ime(), state, controller);
InsetsSource source = new InsetsSource(ID_IME, ime());
source.setFrame(0, 1, 2, 3);
@@ -182,9 +181,9 @@ public class InsetsSourceConsumerTest {
public void testRestore() {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mConsumer.setControl(null, new int[1], new int[1]);
- reset(mMockTransaction);
+ mSurfaceParamsApplied = false;
mController.setRequestedVisibleTypes(0 /* visibleTypes */, statusBars());
- verifyZeroInteractions(mMockTransaction);
+ assertFalse(mSurfaceParamsApplied);
int[] hideTypes = new int[1];
mConsumer.setControl(
new InsetsSourceControl(ID_STATUS_BAR, statusBars(), mLeash,
@@ -200,8 +199,9 @@ public class InsetsSourceConsumerTest {
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
mController.setRequestedVisibleTypes(0 /* visibleTypes */, statusBars());
mConsumer.setControl(null, new int[1], new int[1]);
- reset(mMockTransaction);
- verifyZeroInteractions(mMockTransaction);
+ mLeash = new SurfaceControl.Builder(mSession)
+ .setName("testSurface")
+ .build();
mRemoveSurfaceCalled = false;
int[] hideTypes = new int[1];
mConsumer.setControl(
@@ -221,8 +221,7 @@ public class InsetsSourceConsumerTest {
ViewRootInsetsControllerHost host = new ViewRootInsetsControllerHost(mViewRoot);
InsetsController insetsController = new InsetsController(host, (ic, id, type) -> {
if (type == ime()) {
- return new InsetsSourceConsumer(ID_IME, ime(), state,
- () -> mMockTransaction, ic) {
+ return new InsetsSourceConsumer(ID_IME, ime(), state, ic) {
@Override
public int requestShow(boolean fromController,
ImeTracker.Token statsToken) {
@@ -230,14 +229,14 @@ public class InsetsSourceConsumerTest {
}
};
}
- return new InsetsSourceConsumer(id, type, ic.getState(), Transaction::new, ic);
+ return new InsetsSourceConsumer(id, type, ic.getState(), ic);
}, host.getHandler());
InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(ID_IME, ime());
// Initial IME insets source control with its leash.
imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash,
false /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
- reset(mMockTransaction);
+ mSurfaceParamsApplied = false;
// Verify when the app requests controlling show IME animation, the IME leash
// visibility won't be updated when the consumer received the same leash in setControl.
@@ -246,7 +245,7 @@ public class InsetsSourceConsumerTest {
assertEquals(ANIMATION_TYPE_USER, insetsController.getAnimationType(ime()));
imeConsumer.setControl(new InsetsSourceControl(ID_IME, ime(), mLeash,
true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
- verify(mMockTransaction, never()).show(mLeash);
+ assertFalse(mSurfaceParamsApplied);
});
}
}
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index e07471cd64bc..6d31578ac020 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -128,22 +128,6 @@ public final class Bitmap implements Parcelable {
private static final WeakHashMap<Bitmap, Void> sAllBitmaps = new WeakHashMap<>();
/**
- * @hide
- */
- private static NativeAllocationRegistry getRegistry(boolean malloc, long size) {
- final long free = nativeGetNativeFinalizer();
- if (com.android.libcore.Flags.nativeMetrics()) {
- Class cls = Bitmap.class;
- return malloc ? NativeAllocationRegistry.createMalloced(cls, free, size)
- : NativeAllocationRegistry.createNonmalloced(cls, free, size);
- } else {
- ClassLoader loader = Bitmap.class.getClassLoader();
- return malloc ? NativeAllocationRegistry.createMalloced(loader, free, size)
- : NativeAllocationRegistry.createNonmalloced(loader, free, size);
- }
- }
-
- /**
* Private constructor that must receive an already allocated native bitmap
* int (pointer).
*/
@@ -167,6 +151,7 @@ public final class Bitmap implements Parcelable {
mWidth = width;
mHeight = height;
mRequestPremultiplied = requestPremultiplied;
+
mNinePatchChunk = ninePatchChunk;
mNinePatchInsets = ninePatchInsets;
if (density >= 0) {
@@ -174,9 +159,17 @@ public final class Bitmap implements Parcelable {
}
mNativePtr = nativeBitmap;
- final int allocationByteCount = getAllocationByteCount();
- getRegistry(fromMalloc, allocationByteCount).registerNativeAllocation(this, mNativePtr);
+ final int allocationByteCount = getAllocationByteCount();
+ NativeAllocationRegistry registry;
+ if (fromMalloc) {
+ registry = NativeAllocationRegistry.createMalloced(
+ Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount);
+ } else {
+ registry = NativeAllocationRegistry.createNonmalloced(
+ Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), allocationByteCount);
+ }
+ registry.registerNativeAllocation(this, nativeBitmap);
synchronized (Bitmap.class) {
sAllBitmaps.put(this, null);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 7e6f43458ba6..4607a8ec1210 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -584,7 +584,8 @@ public class ShellTaskOrganizer extends TaskOrganizer {
final boolean windowModeChanged =
data.getTaskInfo().getWindowingMode() != taskInfo.getWindowingMode();
final boolean visibilityChanged = data.getTaskInfo().isVisible != taskInfo.isVisible;
- if (windowModeChanged || visibilityChanged) {
+ if (windowModeChanged || (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
+ && visibilityChanged)) {
mRecentTasks.ifPresent(recentTasks ->
recentTasks.onTaskRunningInfoChanged(taskInfo));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 1573291aef63..3a2820ee3aa9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -358,12 +358,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
if (mode == TRANSIT_CHANGE && change.hasFlags(FLAG_IS_DISPLAY)) {
if (info.getType() == TRANSIT_CHANGE) {
- int anim = getRotationAnimationHint(change, info, mDisplayController);
+ final int anim = getRotationAnimationHint(change, info, mDisplayController);
isSeamlessDisplayChange = anim == ROTATION_ANIMATION_SEAMLESS;
if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) {
- if (wallpaperTransit != WALLPAPER_TRANSITION_NONE) {
- anim |= ScreenRotationAnimation.ANIMATION_HINT_HAS_WALLPAPER;
- }
startRotationAnimation(startTransaction, change, info, anim, animations,
onAnimFinish);
isDisplayRotationAnimationStarted = true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index b9d11a3d0c06..5802e2ca8133 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -25,9 +25,12 @@ import static com.android.wm.shell.transition.DefaultTransitionHandler.buildSurf
import static com.android.wm.shell.transition.Transitions.TAG;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.Context;
+import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
@@ -35,7 +38,6 @@ import android.util.Slog;
import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
-import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.window.ScreenCapture;
@@ -72,9 +74,6 @@ import java.util.ArrayList;
*/
class ScreenRotationAnimation {
static final int MAX_ANIMATION_DURATION = 10 * 1000;
- static final int ANIMATION_HINT_HAS_WALLPAPER = 1 << 8;
- /** It must cover all WindowManager#ROTATION_ANIMATION_*. */
- private static final int ANIMATION_TYPE_MASK = 0xff;
private final Context mContext;
private final TransactionPool mTransactionPool;
@@ -82,7 +81,7 @@ class ScreenRotationAnimation {
/** The leash of the changing window container. */
private final SurfaceControl mSurfaceControl;
- private final int mAnimType;
+ private final int mAnimHint;
private final int mStartWidth;
private final int mStartHeight;
private final int mEndWidth;
@@ -99,12 +98,6 @@ class ScreenRotationAnimation {
private SurfaceControl mBackColorSurface;
/** The leash using to animate screenshot layer. */
private final SurfaceControl mAnimLeash;
- /**
- * The container with background color for {@link #mSurfaceControl}. It is only created if
- * {@link #mSurfaceControl} may be translucent. E.g. visible wallpaper with alpha < 1 (dimmed).
- * That prevents flickering of alpha blending.
- */
- private SurfaceControl mBackEffectSurface;
// The current active animation to move from the old to the new rotated
// state. Which animation is run here will depend on the old and new
@@ -122,7 +115,7 @@ class ScreenRotationAnimation {
Transaction t, TransitionInfo.Change change, SurfaceControl rootLeash, int animHint) {
mContext = context;
mTransactionPool = pool;
- mAnimType = animHint & ANIMATION_TYPE_MASK;
+ mAnimHint = animHint;
mSurfaceControl = change.getLeash();
mStartWidth = change.getStartAbsBounds().width();
@@ -177,20 +170,11 @@ class ScreenRotationAnimation {
}
hardwareBuffer.close();
}
- if ((animHint & ANIMATION_HINT_HAS_WALLPAPER) != 0) {
- mBackEffectSurface = new SurfaceControl.Builder()
- .setCallsite("ShellRotationAnimation").setParent(rootLeash)
- .setEffectLayer().setOpaque(true).setName("BackEffect").build();
- t.reparent(mSurfaceControl, mBackEffectSurface)
- .setColor(mBackEffectSurface,
- new float[] {mStartLuma, mStartLuma, mStartLuma})
- .show(mBackEffectSurface);
- }
t.setLayer(mAnimLeash, SCREEN_FREEZE_LAYER_BASE);
t.show(mAnimLeash);
// Crop the real content in case it contains a larger child layer, e.g. wallpaper.
- t.setCrop(getEnterSurface(), new Rect(0, 0, mEndWidth, mEndHeight));
+ t.setCrop(mSurfaceControl, new Rect(0, 0, mEndWidth, mEndHeight));
if (!isCustomRotate()) {
mBackColorSurface = new SurfaceControl.Builder()
@@ -215,12 +199,7 @@ class ScreenRotationAnimation {
}
private boolean isCustomRotate() {
- return mAnimType == ROTATION_ANIMATION_CROSSFADE || mAnimType == ROTATION_ANIMATION_JUMPCUT;
- }
-
- /** Returns the surface which contains the real content to animate enter. */
- private SurfaceControl getEnterSurface() {
- return mBackEffectSurface != null ? mBackEffectSurface : mSurfaceControl;
+ return mAnimHint == ROTATION_ANIMATION_CROSSFADE || mAnimHint == ROTATION_ANIMATION_JUMPCUT;
}
private void setScreenshotTransform(SurfaceControl.Transaction t) {
@@ -281,7 +260,7 @@ class ScreenRotationAnimation {
final boolean customRotate = isCustomRotate();
if (customRotate) {
mRotateExitAnimation = AnimationUtils.loadAnimation(mContext,
- mAnimType == ROTATION_ANIMATION_JUMPCUT ? R.anim.rotation_animation_jump_exit
+ mAnimHint == ROTATION_ANIMATION_JUMPCUT ? R.anim.rotation_animation_jump_exit
: R.anim.rotation_animation_xfade_exit);
mRotateEnterAnimation = AnimationUtils.loadAnimation(mContext,
R.anim.rotation_animation_enter);
@@ -335,11 +314,7 @@ class ScreenRotationAnimation {
} else {
startDisplayRotation(animations, finishCallback, mainExecutor);
startScreenshotRotationAnimation(animations, finishCallback, mainExecutor);
- if (mBackEffectSurface != null && mStartLuma > 0.1f) {
- // Animate from the color of background to black for smooth alpha blending.
- buildLumaAnimation(animations, mStartLuma, 0f /* endLuma */, mBackEffectSurface,
- animationScale, finishCallback, mainExecutor);
- }
+ //startColorAnimation(mTransaction, animationScale);
}
return true;
@@ -347,7 +322,7 @@ class ScreenRotationAnimation {
private void startDisplayRotation(@NonNull ArrayList<Animator> animations,
@NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
- buildSurfaceAnimation(animations, mRotateEnterAnimation, getEnterSurface(), finishCallback,
+ buildSurfaceAnimation(animations, mRotateEnterAnimation, mSurfaceControl, finishCallback,
mTransactionPool, mainExecutor, null /* position */, 0 /* cornerRadius */,
null /* clipRect */, false /* isActivity */);
}
@@ -366,17 +341,40 @@ class ScreenRotationAnimation {
null /* clipRect */, false /* isActivity */);
}
- private void buildLumaAnimation(@NonNull ArrayList<Animator> animations,
- float startLuma, float endLuma, SurfaceControl surface, float animationScale,
- @NonNull Runnable finishCallback, @NonNull ShellExecutor mainExecutor) {
- final long durationMillis = (long) (mContext.getResources().getInteger(
- R.integer.config_screen_rotation_color_transition) * animationScale);
- final LumaAnimation animation = new LumaAnimation(durationMillis);
- // Align the end with the enter animation.
- animation.setStartOffset(mRotateEnterAnimation.getDuration() - durationMillis);
- final LumaAnimationAdapter adapter = new LumaAnimationAdapter(surface, startLuma, endLuma);
- buildSurfaceAnimation(animations, animation, finishCallback, mTransactionPool,
- mainExecutor, adapter);
+ private void startColorAnimation(float animationScale, @NonNull ShellExecutor animExecutor) {
+ int colorTransitionMs = mContext.getResources().getInteger(
+ R.integer.config_screen_rotation_color_transition);
+ final float[] rgbTmpFloat = new float[3];
+ final int startColor = Color.rgb(mStartLuma, mStartLuma, mStartLuma);
+ final int endColor = Color.rgb(mEndLuma, mEndLuma, mEndLuma);
+ final long duration = colorTransitionMs * (long) animationScale;
+ final Transaction t = mTransactionPool.acquire();
+
+ final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f);
+ // Animation length is already expected to be scaled.
+ va.overrideDurationScale(1.0f);
+ va.setDuration(duration);
+ va.addUpdateListener(animation -> {
+ final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime());
+ final float fraction = currentPlayTime / va.getDuration();
+ applyColor(startColor, endColor, rgbTmpFloat, fraction, mBackColorSurface, t);
+ });
+ va.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
+ t);
+ mTransactionPool.release(t);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ applyColor(startColor, endColor, rgbTmpFloat, 1f /* fraction */, mBackColorSurface,
+ t);
+ mTransactionPool.release(t);
+ }
+ });
+ animExecutor.execute(va::start);
}
public void kill() {
@@ -391,47 +389,21 @@ class ScreenRotationAnimation {
if (mBackColorSurface != null && mBackColorSurface.isValid()) {
t.remove(mBackColorSurface);
}
- if (mBackEffectSurface != null && mBackEffectSurface.isValid()) {
- t.remove(mBackEffectSurface);
- }
t.apply();
mTransactionPool.release(t);
}
- /** A no-op wrapper to provide animation duration. */
- private static class LumaAnimation extends Animation {
- LumaAnimation(long durationMillis) {
- setDuration(durationMillis);
- }
- }
-
- private static class LumaAnimationAdapter extends DefaultTransitionHandler.AnimationAdapter {
- final float[] mColorArray = new float[3];
- final float mStartLuma;
- final float mEndLuma;
- final AccelerateInterpolator mInterpolation;
-
- LumaAnimationAdapter(@NonNull SurfaceControl leash, float startLuma, float endLuma) {
- super(leash);
- mStartLuma = startLuma;
- mEndLuma = endLuma;
- // Make the initial progress color lighter if the background is light. That avoids
- // darker content when fading into the entering surface.
- final float factor = Math.min(3f, (Math.max(0.5f, mStartLuma) - 0.5f) * 10);
- Slog.d(TAG, "Luma=" + mStartLuma + " factor=" + factor);
- mInterpolation = factor > 0.5f ? new AccelerateInterpolator(factor) : null;
- }
-
- @Override
- void applyTransformation(ValueAnimator animator) {
- final float fraction = mInterpolation != null
- ? mInterpolation.getInterpolation(animator.getAnimatedFraction())
- : animator.getAnimatedFraction();
- final float luma = mStartLuma + fraction * (mEndLuma - mStartLuma);
- mColorArray[0] = luma;
- mColorArray[1] = luma;
- mColorArray[2] = luma;
- mTransaction.setColor(mLeash, mColorArray);
+ private static void applyColor(int startColor, int endColor, float[] rgbFloat,
+ float fraction, SurfaceControl surface, SurfaceControl.Transaction t) {
+ final int color = (Integer) ArgbEvaluator.getInstance().evaluate(fraction, startColor,
+ endColor);
+ Color middleColor = Color.valueOf(color);
+ rgbFloat[0] = middleColor.red();
+ rgbFloat[1] = middleColor.green();
+ rgbFloat[2] = middleColor.blue();
+ if (surface.isValid()) {
+ t.setColor(surface, rgbFloat);
}
+ t.apply();
}
}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt
index 717ea306eb77..ce235d445fe5 100644
--- a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithMaxDesktopWindows.kt
@@ -72,7 +72,6 @@ open class StartAppMediaProjectionWithMaxDesktopWindows {
@Test
open fun startMediaProjection() {
- // TODO(b/366455106) - handle max task Limit
mediaProjectionAppHelper.startSingleAppMediaProjection(wmHelper, targetApp)
mailApp.launchViaIntent(wmHelper)
simpleApp.launchViaIntent(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt
index 1573b58853da..f5fb4cec5535 100644
--- a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartAppMediaProjectionWithDisplayRotations.kt
@@ -20,13 +20,12 @@ import android.app.Instrumentation
import android.platform.test.annotations.Postsubmit
import android.tools.NavBar
import android.tools.Rotation
-import android.tools.flicker.rules.ChangeDisplayOrientationRule
import android.tools.device.apphelpers.CalculatorAppHelper
+import android.tools.flicker.rules.ChangeDisplayOrientationRule
import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
import com.android.wm.shell.Utils
import org.junit.After
@@ -47,8 +46,7 @@ open class StartAppMediaProjectionWithDisplayRotations {
private val initialRotation = Rotation.ROTATION_0
private val targetApp = CalculatorAppHelper(instrumentation)
- private val mediaProjectionAppHelper = StartMediaProjectionAppHelper(instrumentation)
- private val testApp = DesktopModeAppHelper(mediaProjectionAppHelper)
+ private val testApp = StartMediaProjectionAppHelper(instrumentation)
@Rule
@JvmField
@@ -63,7 +61,7 @@ open class StartAppMediaProjectionWithDisplayRotations {
@Test
open fun startMediaProjectionAndRotate() {
- mediaProjectionAppHelper.startSingleAppMediaProjection(wmHelper, targetApp)
+ testApp.startSingleAppMediaProjection(wmHelper, targetApp)
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
ChangeDisplayOrientationRule.setRotation(Rotation.ROTATION_90)
diff --git a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt
index e80a895c1aa6..28f3cc758c22 100644
--- a/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt
+++ b/libs/WindowManager/Shell/tests/e2e/mediaprojection/scenarios/src/com/android/wm/shell/scenarios/StartScreenMediaProjectionWithDisplayRotations.kt
@@ -25,7 +25,6 @@ import android.tools.traces.parsers.WindowManagerStateHelper
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.android.launcher3.tapl.LauncherInstrumentation
-import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
import com.android.server.wm.flicker.helpers.StartMediaProjectionAppHelper
import com.android.wm.shell.Utils
import org.junit.After
@@ -45,8 +44,7 @@ open class StartScreenMediaProjectionWithDisplayRotations {
val device = UiDevice.getInstance(instrumentation)
private val initialRotation = Rotation.ROTATION_0
- private val mediaProjectionAppHelper = StartMediaProjectionAppHelper(instrumentation)
- private val testApp = DesktopModeAppHelper(mediaProjectionAppHelper)
+ private val testApp = StartMediaProjectionAppHelper(instrumentation)
@Rule
@JvmField
@@ -60,7 +58,7 @@ open class StartScreenMediaProjectionWithDisplayRotations {
@Test
open fun startMediaProjectionAndRotate() {
- mediaProjectionAppHelper.startEntireScreenMediaProjection(wmHelper)
+ testApp.startEntireScreenMediaProjection(wmHelper)
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
ChangeDisplayOrientationRule.setRotation(Rotation.ROTATION_90)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index e514dc38208e..f01ed84adc74 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -39,6 +39,7 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.never;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.TaskInfo;
@@ -599,6 +600,18 @@ public class ShellTaskOrganizerTests extends ShellTestCase {
}
@Test
+ public void testRecentTasks_visibilityChanges_notFreeForm_shouldNotNotifyTaskController() {
+ RunningTaskInfo task1_visible = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+ mOrganizer.onTaskAppeared(task1_visible, /* leash= */ null);
+ RunningTaskInfo task1_hidden = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+ task1_hidden.isVisible = false;
+
+ mOrganizer.onTaskInfoChanged(task1_hidden);
+
+ verify(mRecentTasksController, never()).onTaskRunningInfoChanged(task1_hidden);
+ }
+
+ @Test
public void testRecentTasks_windowingModeChanges_shouldNotifyTaskController() {
RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
mOrganizer.onTaskAppeared(task1, /* leash= */ null);
diff --git a/media/java/android/media/flags/editing.aconfig b/media/java/android/media/flags/editing.aconfig
index bf6ec9635912..185f579df4b9 100644
--- a/media/java/android/media/flags/editing.aconfig
+++ b/media/java/android/media/flags/editing.aconfig
@@ -8,3 +8,10 @@ flag {
description: "Add media metrics for transcoding/editing events."
bug: "297487694"
}
+
+flag {
+ name: "stagefrightrecorder_enable_b_frames"
+ namespace: "media_solutions"
+ description: "Enable B frames for Stagefright recorder."
+ bug: "341121900"
+}
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml
new file mode 100644
index 000000000000..16ca18ae2200
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v35/settingslib_expressive_icon_chevron.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="18dp"
+ android:height="18dp"
+ android:viewportWidth="960"
+ android:viewportHeight="960"
+ android:tint="@color/settingslib_materialColorOnSurfaceVariant"
+ android:autoMirrored="true">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M321,880L250,809L579,480L250,151L321,80L721,480L321,880Z"/>
+</vector> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml
new file mode 100644
index 000000000000..3f751812ee40
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/layout-v35/settingslib_expressive_two_target_divider.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2024 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/two_target_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="start|center_vertical"
+ android:orientation="horizontal"
+ android:paddingStart="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingLeft="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingVertical="@dimen/settingslib_expressive_space_extrasmall7">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:paddingEnd="@dimen/settingslib_expressive_space_extrasmall6"
+ android:src="@drawable/settingslib_expressive_icon_chevron"/>
+
+ <View
+ android:layout_width="1dp"
+ android:layout_height="40dp"
+ android:background="?android:attr/listDivider" />
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index 3011ce05c3a5..b69912a3fd36 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.7.0-rc01"
+ extra["jetpackComposeVersion"] = "1.7.0"
}
subprojects {
diff --git a/packages/SettingsLib/Spa/gallery/res/values/strings.xml b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
index 18a6db035070..f942fd0662a5 100644
--- a/packages/SettingsLib/Spa/gallery/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
@@ -26,6 +26,8 @@
<string name="single_line_summary_preference_summary" translatable="false">A very long summary to show case a preference which only shows a single line summary.</string>
<!-- Footer text with two links. [DO NOT TRANSLATE] -->
<string name="footer_with_two_links" translatable="false">Annotated string with <a href="https://www.android.com/">link 1</a> and <a href="https://source.android.com/">link 2</a>.</string>
+ <!-- TopIntroPreference preview text. [DO NOT TRANSLATE] -->
+ <string name="label_with_two_links" translatable="false"><a href="https://www.android.com/">Label</a></string>
<!-- Sample title -->
<string name="sample_title" translatable="false">Lorem ipsum</string>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 83d657ef380d..7139f5b468ca 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -42,12 +42,15 @@ import com.android.settingslib.spa.gallery.page.LoadingBarPageProvider
import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
import com.android.settingslib.spa.gallery.scaffold.NonScrollablePagerPageProvider
import com.android.settingslib.spa.gallery.page.SliderPageProvider
+import com.android.settingslib.spa.gallery.preference.IntroPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.ListPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.MainSwitchPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider
import com.android.settingslib.spa.gallery.preference.PreferencePageProvider
import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider
+import com.android.settingslib.spa.gallery.preference.TopIntroPreferencePageProvider
import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider
+import com.android.settingslib.spa.gallery.preference.ZeroStatePreferencePageProvider
import com.android.settingslib.spa.gallery.scaffold.PagerMainPageProvider
import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider
import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider
@@ -82,6 +85,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) {
MainSwitchPreferencePageProvider,
ListPreferencePageProvider,
TwoTargetSwitchPreferencePageProvider,
+ ZeroStatePreferencePageProvider,
ArgumentPageProvider,
SliderPageProvider,
SpinnerPageProvider,
@@ -109,6 +113,8 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) {
SuwScaffoldPageProvider,
BannerPageProvider,
CopyablePageProvider,
+ IntroPreferencePageProvider,
+ TopIntroPreferencePageProvider,
),
rootPages = listOf(
HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
new file mode 100644
index 000000000000..603fceed9900
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/IntroPreferencePageProvider.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AirplanemodeActive
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.preference.IntroPreference
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+
+private const val TITLE = "Sample IntroPreference"
+
+object IntroPreferencePageProvider : SettingsPageProvider {
+ override val name = "IntroPreference"
+ private val owner = createSettingsPage()
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val entryList = mutableListOf<SettingsEntry>()
+ entryList.add(
+ SettingsEntryBuilder.create("IntroPreference", owner)
+ .setUiLayoutFn { SampleIntroPreference() }
+ .build()
+ )
+
+ return entryList
+ }
+
+ fun buildInjectEntry(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
+ Preference(
+ object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ }
+ )
+ }
+ }
+
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
+}
+
+@Composable
+private fun SampleIntroPreference() {
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ IntroPreference(
+ title = "Preferred network type",
+ descriptions = listOf("Description"),
+ imageVector = Icons.Outlined.AirplanemodeActive,
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
index ce9678bab684..1626b025e2f7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMainPageProvider.kt
@@ -39,6 +39,9 @@ object PreferenceMainPageProvider : SettingsPageProvider {
ListPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
TwoTargetSwitchPreferencePageProvider.buildInjectEntry()
.setLink(fromPage = owner).build(),
+ ZeroStatePreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ IntroPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+ TopIntroPreferencePageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
)
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt
new file mode 100644
index 000000000000..b251266e0574
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TopIntroPreferencePageProvider.kt
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import com.android.settingslib.spa.gallery.R
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.TopIntroPreference
+import com.android.settingslib.spa.widget.preference.TopIntroPreferenceModel
+
+private const val TITLE = "Sample TopIntroPreference"
+
+object TopIntroPreferencePageProvider : SettingsPageProvider {
+ override val name = "TopIntroPreference"
+ private val owner = createSettingsPage()
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val entryList = mutableListOf<SettingsEntry>()
+ entryList.add(
+ SettingsEntryBuilder.create("TopIntroPreference", owner)
+ .setUiLayoutFn { SampleTopIntroPreference() }
+ .build()
+ )
+
+ return entryList
+ }
+
+ fun buildInjectEntry(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(owner).setUiLayoutFn {
+ Preference(
+ object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ }
+ )
+ }
+ }
+
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
+}
+
+@Composable
+private fun SampleTopIntroPreference() {
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ TopIntroPreference(
+ object : TopIntroPreferenceModel {
+ override val text =
+ "Additional text needed for the page. This can sit on the right side of the screen in 2 column.\n" +
+ "Example collapsed text area that you will not see until you expand this block."
+ override val expandText = "Expand"
+ override val collapseText = "Collapse"
+ override val labelText = R.string.label_with_two_links
+ }
+ )
+ }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.kt
new file mode 100644
index 000000000000..4a9c5c8fad4f
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/ZeroStatePreferencePageProvider.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.settingslib.spa.gallery.preference
+
+import android.os.Bundle
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.History
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.createSettingsPage
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.preference.ZeroStatePreference
+
+private const val TITLE = "Sample ZeroStatePreference"
+
+object ZeroStatePreferencePageProvider : SettingsPageProvider {
+ override val name = "ZeroStatePreference"
+ private val owner = createSettingsPage()
+
+ override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+ val entryList = mutableListOf<SettingsEntry>()
+ entryList.add(
+ SettingsEntryBuilder.create("ZeroStatePreference", owner)
+ .setUiLayoutFn {
+ SampleZeroStatePreference()
+ }.build()
+ )
+
+ return entryList
+ }
+
+ fun buildInjectEntry(): SettingsEntryBuilder {
+ return SettingsEntryBuilder.createInject(owner)
+ .setUiLayoutFn {
+ Preference(object : PreferenceModel {
+ override val title = TITLE
+ override val onClick = navigator(name)
+ })
+ }
+ }
+
+ override fun getTitle(arguments: Bundle?): String {
+ return TITLE
+ }
+}
+
+@Composable
+private fun SampleZeroStatePreference() {
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ ZeroStatePreference(
+ Icons.Filled.History,
+ "No recent search history",
+ "Description"
+ )
+ }
+}
+
+
+@Preview(showBackground = true)
+@Composable
+private fun SwitchPreferencePagePreview() {
+ SettingsTheme {
+ ZeroStatePreferencePageProvider.Page(null)
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index f0c2ea6f5353..790aa9ff8d3f 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -54,15 +54,16 @@ android {
dependencies {
api(project(":SettingsLibColor"))
api("androidx.appcompat:appcompat:1.7.0")
- api("androidx.compose.material3:material3:1.3.0-rc01")
+ api("androidx.compose.material3:material3:1.3.0")
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.8.0-rc01")
+ api("androidx.navigation:navigation-compose:2.8.1")
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
api("com.google.android.material:material:1.11.0")
+ api("androidx.graphics:graphics-shapes-android:1.0.1")
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
implementation("com.airbnb.android:lottie-compose:6.4.0")
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 1f3e24254027..f8c791aab0d0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -21,7 +21,9 @@ import androidx.compose.ui.unit.dp
object SettingsDimension {
val paddingTiny = 2.dp
- val paddingSmall = 4.dp
+ val paddingExtraSmall = 4.dp
+ val paddingSmall = if (isSpaExpressiveEnabled) 8.dp else 4.dp
+ val paddingExtraSmall5 = 10.dp
val paddingLarge = 16.dp
val paddingExtraLarge = 24.dp
@@ -56,6 +58,7 @@ object SettingsDimension {
val itemDividerHeight = 32.dp
val iconLarge = 48.dp
+ val introIconSize = 40.dp
/** The size when app icon is displayed in list. */
val appIconItemSize = 32.dp
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
index 15def728d8b3..f948d5163177 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsTheme.kt
@@ -21,6 +21,7 @@ import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
+import com.android.settingslib.spa.framework.util.SystemProperties
/**
* The Material 3 Theme for Settings.
@@ -41,4 +42,5 @@ fun SettingsTheme(content: @Composable () -> Unit) {
}
}
-const val isSpaExpressiveEnabled = false \ No newline at end of file
+val isSpaExpressiveEnabled
+ by lazy { SystemProperties.getBoolean("is_expressive_design_enabled", false) }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SystemProperties.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SystemProperties.kt
new file mode 100644
index 000000000000..ed4936bbf8c9
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/SystemProperties.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import android.annotation.SuppressLint
+import android.util.Log
+
+@SuppressLint("PrivateApi")
+object SystemProperties {
+ private const val TAG = "SystemProperties"
+
+ fun getBoolean(key: String, default: Boolean): Boolean = try {
+ val systemProperties = Class.forName("android.os.SystemProperties")
+ systemProperties
+ .getMethod("getBoolean", String::class.java, Boolean::class.java)
+ .invoke(systemProperties, key, default) as Boolean
+ } catch (e: Exception) {
+ Log.e(TAG, "getBoolean: $key", e)
+ default
+ }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
new file mode 100644
index 000000000000..22a57554eeaf
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/IntroPreference.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.preference
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.AirplanemodeActive
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+
+@Composable
+fun IntroPreference(
+ title: String,
+ descriptions: List<String>? = null,
+ imageVector: ImageVector? = null,
+) {
+ IntroPreference(title = title, descriptions = descriptions, icon = { IntroIcon(imageVector) })
+}
+
+@Composable
+fun IntroAppPreference(
+ title: String,
+ descriptions: List<String>? = null,
+ appIcon: @Composable (() -> Unit),
+) {
+ IntroPreference(title = title, descriptions = descriptions, icon = { IntroAppIcon(appIcon) })
+}
+
+@Composable
+internal fun IntroPreference(
+ title: String,
+ descriptions: List<String>?,
+ icon: @Composable (() -> Unit),
+) {
+ Column(
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(
+ horizontal = SettingsDimension.paddingExtraLarge,
+ vertical = SettingsDimension.paddingLarge,
+ ),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ icon()
+ IntroTitle(title)
+ IntroDescription(descriptions)
+ }
+}
+
+@Composable
+private fun IntroIcon(imageVector: ImageVector?) {
+ if (imageVector != null) {
+ Box(
+ modifier =
+ Modifier.size(SettingsDimension.itemIconContainerSize)
+ .clip(CircleShape)
+ .background(MaterialTheme.colorScheme.secondaryContainer),
+ contentAlignment = Alignment.Center,
+ ) {
+ Icon(
+ imageVector = imageVector,
+ contentDescription = null,
+ modifier = Modifier.size(SettingsDimension.introIconSize),
+ tint = MaterialTheme.colorScheme.onSecondary,
+ )
+ }
+ }
+}
+
+@Composable
+private fun IntroAppIcon(appIcon: @Composable () -> Unit) {
+ Box(
+ modifier = Modifier.size(SettingsDimension.itemIconContainerSize).clip(CircleShape),
+ contentAlignment = Alignment.Center,
+ ) {
+ appIcon()
+ }
+}
+
+@Composable
+private fun IntroTitle(title: String) {
+ Box(modifier = Modifier.padding(top = SettingsDimension.paddingLarge)) {
+ Text(
+ text = title,
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.titleLarge,
+ color = MaterialTheme.colorScheme.onSurface,
+ )
+ }
+}
+
+@Composable
+private fun IntroDescription(descriptions: List<String>?) {
+ if (descriptions != null) {
+ for (description in descriptions) {
+ if (description.isEmpty()) continue
+ Text(
+ text = description,
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.titleMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.padding(top = SettingsDimension.paddingExtraSmall),
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun IntroPreferencePreview() {
+ IntroPreference(
+ title = "Preferred network type",
+ descriptions = listOf("Description", "Version"),
+ imageVector = Icons.Outlined.AirplanemodeActive,
+ )
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TopIntroPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TopIntroPreference.kt
new file mode 100644
index 000000000000..7e619591c8a9
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TopIntroPreference.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.preference
+
+import androidx.annotation.StringRes
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.KeyboardArrowDown
+import androidx.compose.material.icons.filled.KeyboardArrowUp
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spa.framework.theme.toMediumWeight
+import com.android.settingslib.spa.framework.util.annotatedStringResource
+
+/** The widget model for [TopIntroPreference] widget. */
+interface TopIntroPreferenceModel {
+ /** The content of this [TopIntroPreference]. */
+ val text: String
+
+ /** The text clicked to expand this [TopIntroPreference]. */
+ val expandText: String
+
+ /** The text clicked to collapse this [TopIntroPreference]. */
+ val collapseText: String
+
+ /** The text clicked to open other resources. Should be a resource Id. */
+ val labelText: Int?
+}
+
+@Composable
+fun TopIntroPreference(model: TopIntroPreferenceModel) {
+ var expanded by remember { mutableStateOf(false) }
+ Column(Modifier.background(MaterialTheme.colorScheme.surfaceContainer)) {
+ // TopIntroPreference content.
+ Column(
+ modifier =
+ Modifier.padding(
+ horizontal = SettingsDimension.paddingExtraLarge,
+ vertical = SettingsDimension.paddingSmall,
+ )
+ .animateContentSize()
+ ) {
+ Text(
+ text = model.text,
+ style = MaterialTheme.typography.bodyLarge,
+ maxLines = if (expanded) MAX_LINE else MIN_LINE,
+ )
+ if (expanded) TopIntroAnnotatedText(model.labelText)
+ }
+
+ // TopIntroPreference collapse bar.
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier =
+ Modifier.fillMaxWidth()
+ .clickable(onClick = { expanded = !expanded })
+ .padding(
+ top = SettingsDimension.paddingSmall,
+ bottom = SettingsDimension.paddingLarge,
+ start = SettingsDimension.paddingExtraLarge,
+ end = SettingsDimension.paddingExtraLarge,
+ ),
+ ) {
+ Icon(
+ imageVector =
+ if (expanded) Icons.Filled.KeyboardArrowUp else Icons.Filled.KeyboardArrowDown,
+ contentDescription = null,
+ modifier =
+ Modifier.size(SettingsDimension.itemIconSize)
+ .clip(CircleShape)
+ .background(MaterialTheme.colorScheme.surfaceContainerHighest),
+ )
+ Text(
+ text = if (expanded) model.collapseText else model.expandText,
+ modifier = Modifier.padding(start = SettingsDimension.paddingSmall),
+ style = MaterialTheme.typography.bodyLarge.toMediumWeight(),
+ color = MaterialTheme.colorScheme.onSurface,
+ )
+ }
+ }
+}
+
+@Composable
+private fun TopIntroAnnotatedText(@StringRes id: Int?) {
+ if (id != null) {
+ Box(
+ Modifier.padding(
+ top = SettingsDimension.paddingExtraSmall5,
+ bottom = SettingsDimension.paddingExtraSmall5,
+ end = SettingsDimension.paddingLarge,
+ )
+ ) {
+ Text(
+ text = annotatedStringResource(id),
+ style = MaterialTheme.typography.bodyLarge.toMediumWeight(),
+ color = MaterialTheme.colorScheme.primary,
+ )
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun TopIntroPreferencePreview() {
+ TopIntroPreference(
+ object : TopIntroPreferenceModel {
+ override val text =
+ "Additional text needed for the page. This can sit on the right side of the screen in 2 column.\n" +
+ "Example collapsed text area that you will not see until you expand this block."
+ override val expandText = "Expand"
+ override val collapseText = "Collapse"
+ override val labelText = androidx.appcompat.R.string.abc_prepend_shortcut_label
+ }
+ )
+}
+
+const val MIN_LINE = 2
+const val MAX_LINE = 10
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
new file mode 100644
index 000000000000..3f2e7723c585
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/ZeroStatePreference.kt
@@ -0,0 +1,132 @@
+/*
+ * 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.preference
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.History
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Matrix
+import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.asComposePath
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.graphics.shapes.CornerRounding
+import androidx.graphics.shapes.RoundedPolygon
+import androidx.graphics.shapes.star
+import androidx.graphics.shapes.toPath
+
+@Composable
+fun ZeroStatePreference(icon: ImageVector, text: String? = null, description: String? = null) {
+ val zeroStateShape = remember {
+ RoundedPolygon.star(
+ numVerticesPerRadius = 6,
+ innerRadius = 0.75f,
+ rounding = CornerRounding(0.3f)
+ )
+ }
+ val clip = remember(zeroStateShape) {
+ RoundedPolygonShape(polygon = zeroStateShape)
+ }
+ Column(horizontalAlignment = Alignment.CenterHorizontally) {
+ Box(
+ modifier = Modifier
+ .clip(clip)
+ .background(MaterialTheme.colorScheme.primary)
+ .size(160.dp)
+ ) {
+ Icon(
+ imageVector = icon,
+ modifier = Modifier
+ .align(Alignment.Center)
+ .size(72.dp),
+ tint = MaterialTheme.colorScheme.onPrimary,
+ contentDescription = null,
+ )
+ }
+ if (text != null) {
+ Text(
+ text = text,
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.titleMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier.padding(top = 24.dp),
+ )
+ }
+ if (description != null) {
+ Box {
+ Text(
+ text = description,
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun ZeroStatePreferencePreview() {
+ ZeroStatePreference(
+ Icons.Filled.History,
+ "No recent search history",
+ "Description"
+ )
+}
+
+class RoundedPolygonShape(
+ private val polygon: RoundedPolygon,
+ private var matrix: Matrix = Matrix()
+) : Shape {
+ private var path = Path()
+ override fun createOutline(
+ size: Size,
+ layoutDirection: LayoutDirection,
+ density: Density
+ ): Outline {
+ path.rewind()
+ path = polygon.toPath().asComposePath()
+
+ matrix.reset()
+ matrix.scale(size.width / 2f, size.height / 2f)
+ matrix.translate(1f, 1f)
+ matrix.rotateZ(30.0f)
+
+ path.transform(matrix)
+ return Outline.Generic(path)
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/res/values/strings.xml b/packages/SettingsLib/Spa/tests/res/values/strings.xml
index fb8f878230d5..346f69bb7a42 100644
--- a/packages/SettingsLib/Spa/tests/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/tests/res/values/strings.xml
@@ -28,5 +28,7 @@
<string name="test_annotated_string_resource">Annotated string with <b>bold</b> and <a href="https://www.android.com/">link</a>.</string>
+ <string name="test_top_intro_preference_label"><a href="https://www.android.com/">Label</a></string>
+
<string name="test_link"><a href="https://www.android.com/">link</a></string>
</resources>
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SystemPropertiesTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SystemPropertiesTest.kt
new file mode 100644
index 000000000000..0827fa9e0ae0
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/SystemPropertiesTest.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SystemPropertiesTest {
+
+ @Test
+ fun getBoolean_noCrash() {
+ SystemProperties.getBoolean("is_expressive_design_enabled", false)
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt
new file mode 100644
index 000000000000..5d801451adcb
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/IntroPreferenceTest.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class IntroPreferenceTest {
+ @get:Rule val composeTestRule = createComposeRule()
+
+ @Test
+ fun title_displayed() {
+ composeTestRule.setContent { IntroPreference(title = TITLE) }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun description_displayed() {
+ composeTestRule.setContent { IntroPreference(title = TITLE, descriptions = DESCRIPTION) }
+
+ composeTestRule.onNodeWithText(DESCRIPTION.component1()).assertIsDisplayed()
+ composeTestRule.onNodeWithText(DESCRIPTION.component2()).assertIsNotDisplayed()
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ val DESCRIPTION = listOf("Description", "")
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TopIntroPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TopIntroPreferenceTest.kt
new file mode 100644
index 000000000000..62a71d4763b3
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TopIntroPreferenceTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.test.R
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class TopIntroPreferenceTest {
+ @get:Rule val composeTestRule = createComposeRule()
+
+ @Test
+ fun content_collapsed_displayed() {
+ composeTestRule.setContent {
+ TopIntroPreference(
+ object : TopIntroPreferenceModel {
+ override val text = TEXT
+ override val expandText = EXPAND_TEXT
+ override val collapseText = COLLAPSE_TEXT
+ override val labelText = R.string.test_top_intro_preference_label
+ }
+ )
+ }
+
+ composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+ composeTestRule.onNodeWithText(EXPAND_TEXT).assertIsDisplayed()
+ }
+
+ @Test
+ fun content_expended_displayed() {
+ composeTestRule.setContent {
+ TopIntroPreference(
+ object : TopIntroPreferenceModel {
+ override val text = TEXT
+ override val expandText = EXPAND_TEXT
+ override val collapseText = COLLAPSE_TEXT
+ override val labelText = R.string.test_top_intro_preference_label
+ }
+ )
+ }
+
+ composeTestRule.onNodeWithText(TEXT).assertIsDisplayed()
+ composeTestRule.onNodeWithText(EXPAND_TEXT).assertIsDisplayed().performClick()
+ composeTestRule.onNodeWithText(COLLAPSE_TEXT).assertIsDisplayed()
+ composeTestRule.onNodeWithText(LABEL_TEXT).assertIsDisplayed()
+ }
+
+ private companion object {
+ const val TEXT = "Text"
+ const val EXPAND_TEXT = "Expand"
+ const val COLLAPSE_TEXT = "Collapse"
+ const val LABEL_TEXT = "Label"
+ }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ZeroStatePreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ZeroStatePreferenceTest.kt
new file mode 100644
index 000000000000..99ac27c36e46
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/ZeroStatePreferenceTest.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.History
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ZeroStatePreferenceTest {
+ @get:Rule
+ val composeTestRule = createComposeRule()
+
+ @Test
+ fun title_displayed() {
+ composeTestRule.setContent {
+ ZeroStatePreference(Icons.Filled.History, TITLE)
+ }
+
+ composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+ }
+
+ @Test
+ fun description_displayed() {
+ composeTestRule.setContent {
+ ZeroStatePreference(Icons.Filled.History, TITLE, DESCRIPTION)
+ }
+
+ composeTestRule.onNodeWithText(DESCRIPTION).assertIsDisplayed()
+ }
+
+ private companion object {
+ const val TITLE = "Title"
+ const val DESCRIPTION = "Description"
+ }
+} \ No newline at end of file
diff --git a/packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml b/packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml
new file mode 100644
index 000000000000..4347ef29037d
--- /dev/null
+++ b/packages/SettingsLib/TwoTargetPreference/res/layout-v35/settingslib_expressive_preference_two_target.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2024 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:gravity="center_vertical"
+ android:background="?android:attr/selectableItemBackground"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:clipToPadding="false">
+
+ <include layout="@layout/settingslib_expressive_preference_icon_frame"/>
+
+ <include layout="@layout/settingslib_expressive_preference_text_frame" />
+
+ <include layout="@layout/settingslib_expressive_two_target_divider" />
+
+ <!-- Preference should place its actual preference widget here. -->
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:minWidth="@dimen/two_target_min_width"
+ android:gravity="center"
+ android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
index b125f716fe52..58ff0ce1932a 100644
--- a/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
+++ b/packages/SettingsLib/TwoTargetPreference/src/com/android/settingslib/widget/TwoTargetPreference.java
@@ -72,7 +72,10 @@ public class TwoTargetPreference extends Preference {
}
private void init(Context context) {
- setLayoutResource(R.layout.preference_two_target);
+ int resID = SettingsThemeHelper.isExpressiveTheme(context)
+ ? R.layout.settingslib_expressive_preference_two_target
+ : R.layout.preference_two_target;
+ setLayoutResource(resID);
mSmallIconSize = context.getResources().getDimensionPixelSize(
R.dimen.two_target_pref_small_icon_size);
mMediumIconSize = context.getResources().getDimensionPixelSize(
diff --git a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
index e41126f03c60..2475c8e9dfd1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java
@@ -31,6 +31,8 @@ import androidx.preference.PreferenceViewHolder;
import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
import com.android.settingslib.core.instrumentation.SettingsJankMonitor;
+import com.android.settingslib.widget.SettingsThemeHelper;
+import com.android.settingslib.widget.theme.R;
/**
* A custom preference that provides inline switch toggle. It has a mandatory field for title, and
@@ -62,7 +64,9 @@ public class PrimarySwitchPreference extends RestrictedPreference {
@Override
protected int getSecondTargetResId() {
- return androidx.preference.R.layout.preference_widget_switch_compat;
+ return SettingsThemeHelper.isExpressiveTheme(getContext())
+ ? R.layout.settingslib_expressive_preference_switch
+ : androidx.preference.R.layout.preference_widget_switch_compat;
}
@Override
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index c49ffb49a1da..f8383d94b1ab 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -149,16 +149,6 @@ flag {
}
flag {
- name: "modes_dialog_single_rows"
- namespace: "systemui"
- description: "[Experiment] Display one entry per grid row in the Modes Dialog."
- bug: "366034002"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "pss_app_selector_recents_split_screen"
namespace: "systemui"
description: "Allows recent apps selected for partial screenshare to be launched in split screen mode"
@@ -599,6 +589,16 @@ flag {
}
flag {
+ name: "clipboard_use_description_mimetype"
+ namespace: "systemui"
+ description: "Read item mimetype from description rather than checking URI"
+ bug: "357197236"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "screenshot_action_dismiss_system_windows"
namespace: "systemui"
description: "Dismiss existing system windows when starting action from screenshot UI"
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
index 6ee8ffd91ab0..6ee8ffd91ab0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/AuthKeyguardMessageAreaTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
index 049d77a7a454..049d77a7a454 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/BouncerKeyguardMessageAreaTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
index bb2340aafbb5..bb2340aafbb5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
index c42e25b20e0d..c42e25b20e0d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/EmergencyButtonControllerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
index d170e4840842..d170e4840842 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index 2bb9e68a357a..2bb9e68a357a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 892375d002c1..892375d002c1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt
index c2c0f5713d9b..c2c0f5713d9b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchControllerWithCoroutinesTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 0bf9d12a09d5..0bf9d12a09d5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardClockSwitchTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
index dd58ea7db2bc..dd58ea7db2bc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardDisplayManagerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index bd811814eb24..bd811814eb24 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSliceViewTest.java
index d96518abc007..d96518abc007 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSliceViewTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
index 64e499674d9f..64e499674d9f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusAreaViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
index 2b4fc5bd5cc5..2b4fc5bd5cc5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewControllerBaseTest.java
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
index c29439d89753..c29439d89753 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewControllerWithCoroutinesTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewTest.kt
index 16d2f0205c84..16d2f0205c84 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardStatusViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
index 2e41246a62a1..2e41246a62a1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt
index e7b4262419ce..e7b4262419ce 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/NumPadAnimatorTest.kt
index 18976e135e9c..18976e135e9c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/NumPadAnimatorTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/PinShapeHintingViewTest.kt
index d8f2b1016657..d8f2b1016657 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/PinShapeHintingViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/PinShapeHintingViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt
index 447cf65ba293..447cf65ba293 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/PinShapeNonHintingViewTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
index c7d11ef16100..c7d11ef16100 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TestScopeProvider.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/TestScopeProvider.kt
index 6c35734c6eb4..6c35734c6eb4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/TestScopeProvider.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/TestScopeProvider.kt
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
index 5247a89896d1..5247a89896d1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
index 2ba4bf930f3f..e25c1a71a5a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractorTest.kt
@@ -34,8 +34,6 @@ import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.realKeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.DozeStateModel
-import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
@@ -50,8 +48,6 @@ 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.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
-import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlin.time.Duration.Companion.seconds
@@ -220,15 +216,11 @@ class CommunalSceneTransitionInteractorTest : SysuiTestCase() {
@Test
fun transition_from_hub_end_in_dream() =
testScope.runTest {
- // Device is dreaming and not dozing.
- kosmos.powerInteractor.setAwakeForTest()
- kosmos.fakeKeyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
- )
+ // Device is dreaming and occluded.
kosmos.fakeKeyguardRepository.setKeyguardOccluded(true)
kosmos.fakeKeyguardRepository.setDreaming(true)
kosmos.fakeKeyguardRepository.setDreamingWithOverlay(true)
- advanceTimeBy(600L)
+ runCurrent()
sceneTransitions.value = hubToBlank
@@ -663,7 +655,7 @@ class CommunalSceneTransitionInteractorTest : SysuiTestCase() {
from = LOCKSCREEN,
to = OCCLUDED,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -750,7 +742,7 @@ class CommunalSceneTransitionInteractorTest : SysuiTestCase() {
from = LOCKSCREEN,
to = OCCLUDED,
animator = null,
- modeOnCanceled = TransitionModeOnCanceled.RESET
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
)
)
@@ -852,8 +844,8 @@ class CommunalSceneTransitionInteractorTest : SysuiTestCase() {
to = ALTERNATE_BOUNCER,
animator = null,
ownerName = "external",
- modeOnCanceled = TransitionModeOnCanceled.RESET
- ),
+ modeOnCanceled = TransitionModeOnCanceled.RESET,
+ )
)
val allSteps by collectValues(keyguardTransitionRepository.transitions)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt
index 9da68853a5aa..52ed23122fde 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialCoreStartableTest.kt
@@ -19,11 +19,13 @@ package com.android.systemui.inputdevice.tutorial
import android.content.Context
import android.content.Intent
import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.inputdevice.tutorial.ui.TutorialNotificationCoordinator
+import com.android.systemui.shared.Flags
import com.android.systemui.testKosmos
import org.junit.Test
import org.junit.runner.RunWith
@@ -43,16 +45,17 @@ class KeyboardTouchpadTutorialCoreStartableTest : SysuiTestCase() {
KeyboardTouchpadTutorialCoreStartable(
{ mock<TutorialNotificationCoordinator>() },
broadcastDispatcher,
- context
+ context,
)
@Test
+ @EnableFlags(Flags.FLAG_NEW_TOUCHPAD_GESTURES_TUTORIAL)
fun registersBroadcastReceiverStartingActivityAsSystemUser() {
underTest.start()
broadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
- Intent("com.android.systemui.action.KEYBOARD_TOUCHPAD_TUTORIAL")
+ Intent("com.android.systemui.action.KEYBOARD_TOUCHPAD_TUTORIAL"),
)
verify(context).startActivityAsUser(any(), eq(UserHandle.SYSTEM))
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 bd6cffff6162..93754fd7e778 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
@@ -31,6 +31,7 @@ import com.android.systemui.doze.DozeMachine
import com.android.systemui.doze.DozeTransitionCallback
import com.android.systemui.doze.DozeTransitionListener
import com.android.systemui.dreams.DreamOverlayCallbackController
+import com.android.systemui.flags.DisableSceneContainer
import com.android.systemui.keyguard.shared.model.BiometricUnlockMode
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import com.android.systemui.keyguard.shared.model.DozeStateModel
@@ -288,6 +289,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() {
}
@Test
+ @DisableSceneContainer
fun dozeAmount() =
testScope.runTest {
val values = mutableListOf<Float>()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index fac931273ac7..ff0a4a16fe2a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -26,8 +26,10 @@ import com.android.compose.animation.scene.ObservableTransitionState
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR
+import com.android.systemui.Flags.FLAG_SCENE_CONTAINER
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
+import com.android.systemui.communal.data.repository.communalSceneRepository
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.domain.interactor.setCommunalAvailable
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -50,10 +52,12 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
+import com.google.common.truth.Truth
import junit.framework.Assert.assertEquals
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -219,6 +223,28 @@ class FromDozingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiT
}
@Test
+ @DisableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR, FLAG_SCENE_CONTAINER)
+ @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ fun testTransitionToGlanceableHub_onWakeup_ifAvailable() =
+ testScope.runTest {
+ // Hub is available.
+ whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+
+ // Device turns on.
+ powerInteractor.setAwakeForTest()
+ advanceTimeBy(50L)
+ runCurrent()
+
+ // We transition to the hub when waking up.
+ Truth.assertThat(kosmos.communalSceneRepository.currentScene.value)
+ .isEqualTo(CommunalScenes.Communal)
+ // No transitions are directly started by this interactor.
+ assertThat(transitionRepository).noTransitionsStarted()
+ }
+
+ @Test
@EnableFlags(FLAG_KEYGUARD_WM_STATE_REFACTOR)
fun testTransitionToOccluded_onWakeup_whenOccludingActivityOnTop() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
index 638c957c9fa7..a08fbbf75805 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorTest.kt
@@ -16,12 +16,19 @@
package com.android.systemui.keyguard.domain.interactor
+import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
-import androidx.test.ext.junit.runners.AndroidJUnit4
+import android.platform.test.flag.junit.FlagsParameterization
+import android.service.dream.dreamManager
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_COMMUNAL_SCENE_KTF_REFACTOR
import com.android.systemui.SysuiTestCase
import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository
+import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.communal.domain.interactor.setCommunalAvailable
+import com.android.systemui.communal.shared.model.CommunalScenes
+import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
@@ -36,6 +43,7 @@ import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInterac
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
@@ -43,26 +51,52 @@ import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
+import org.mockito.kotlin.whenever
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidJUnit4::class)
-class FromDreamingTransitionInteractorTest : SysuiTestCase() {
+@RunWith(ParameterizedAndroidJunit4::class)
+class FromDreamingTransitionInteractorTest(flags: FlagsParameterization?) : SysuiTestCase() {
+ companion object {
+ @JvmStatic
+ @Parameters(name = "{0}")
+ fun getParams(): List<FlagsParameterization> {
+ return FlagsParameterization.allCombinationsOf(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ .andSceneContainer()
+ }
+ }
+
+ init {
+ mSetFlagsRule.setFlagsParameterization(flags!!)
+ }
+
private val kosmos =
testKosmos().apply {
this.fakeKeyguardTransitionRepository = spy(FakeKeyguardTransitionRepository())
}
private val testScope = kosmos.testScope
- private val underTest = kosmos.fromDreamingTransitionInteractor
+ private val underTest by lazy { kosmos.fromDreamingTransitionInteractor }
private val powerInteractor = kosmos.powerInteractor
private val transitionRepository = kosmos.fakeKeyguardTransitionRepository
@Before
fun setup() {
+ runBlocking {
+ transitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.DREAMING,
+ testScope,
+ )
+ reset(transitionRepository)
+ kosmos.setCommunalAvailable(true)
+ }
underTest.start()
}
@@ -86,10 +120,7 @@ class FromDreamingTransitionInteractorTest : SysuiTestCase() {
runCurrent()
assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.DREAMING,
- to = KeyguardState.OCCLUDED,
- )
+ .startedTransition(from = KeyguardState.DREAMING, to = KeyguardState.OCCLUDED)
}
@Test
@@ -126,7 +157,7 @@ class FromDreamingTransitionInteractorTest : SysuiTestCase() {
transitionRepository.sendTransitionSteps(
from = KeyguardState.LOCKSCREEN,
to = KeyguardState.DREAMING,
- testScope
+ testScope,
)
kosmos.fakeKeyguardRepository.setBiometricUnlockState(BiometricUnlockMode.NONE)
@@ -139,10 +170,7 @@ class FromDreamingTransitionInteractorTest : SysuiTestCase() {
advanceTimeBy(60L)
assertThat(transitionRepository)
- .startedTransition(
- from = KeyguardState.DREAMING,
- to = KeyguardState.LOCKSCREEN,
- )
+ .startedTransition(from = KeyguardState.DREAMING, to = KeyguardState.LOCKSCREEN)
}
@Test
@@ -164,4 +192,25 @@ class FromDreamingTransitionInteractorTest : SysuiTestCase() {
to = KeyguardState.ALTERNATE_BOUNCER,
)
}
+
+ @Test
+ @EnableFlags(FLAG_COMMUNAL_SCENE_KTF_REFACTOR)
+ @DisableFlags(Flags.FLAG_SCENE_CONTAINER)
+ fun testTransitionToGlanceableHubOnWake() =
+ testScope.runTest {
+ whenever(kosmos.dreamManager.canStartDreaming(anyBoolean())).thenReturn(true)
+ kosmos.setCommunalAvailable(true)
+ runCurrent()
+
+ // Device wakes up.
+ powerInteractor.setAwakeForTest()
+ advanceTimeBy(150L)
+ runCurrent()
+
+ // We transition to the hub when waking up.
+ assertThat(kosmos.communalSceneRepository.currentScene.value)
+ .isEqualTo(CommunalScenes.Communal)
+ // No transitions are directly started by this interactor.
+ assertThat(transitionRepository).noTransitionsStarted()
+ }
}
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 b843fd508616..3fb3eead6469 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
@@ -29,15 +29,21 @@ import com.android.systemui.common.ui.data.repository.fakeConfigurationRepositor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
import com.android.systemui.flags.EnableSceneContainer
-import com.android.systemui.keyguard.data.repository.fakeCommandQueue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.keyguard.shared.model.CameraLaunchType
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
+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.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
+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.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
@@ -67,12 +73,11 @@ class KeyguardInteractorTest : SysuiTestCase() {
private val testScope = kosmos.testScope
private val repository by lazy { kosmos.fakeKeyguardRepository }
private val sceneInteractor by lazy { kosmos.sceneInteractor }
- private val fromGoneTransitionInteractor by lazy { kosmos.fromGoneTransitionInteractor }
- private val commandQueue by lazy { kosmos.fakeCommandQueue }
private val configRepository by lazy { kosmos.fakeConfigurationRepository }
private val bouncerRepository by lazy { kosmos.keyguardBouncerRepository }
private val shadeRepository by lazy { kosmos.shadeRepository }
private val powerInteractor by lazy { kosmos.powerInteractor }
+ private val keyguardRepository by lazy { kosmos.keyguardRepository }
private val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
private val transitionState: MutableStateFlow<ObservableTransitionState> =
@@ -178,8 +183,8 @@ class KeyguardInteractorTest : SysuiTestCase() {
assertThat(dismissAlpha).isEqualTo(1f)
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
+ from = AOD,
+ to = LOCKSCREEN,
testScope,
)
@@ -204,8 +209,8 @@ class KeyguardInteractorTest : SysuiTestCase() {
assertThat(dismissAlpha.size).isEqualTo(1)
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
+ from = AOD,
+ to = LOCKSCREEN,
testScope,
)
@@ -266,13 +271,13 @@ class KeyguardInteractorTest : SysuiTestCase() {
keyguardTransitionRepository.sendTransitionSteps(
listOf(
TransitionStep(
- from = KeyguardState.AOD,
+ from = AOD,
to = KeyguardState.GONE,
value = 0f,
- transitionState = TransitionState.STARTED,
+ transitionState = STARTED,
),
TransitionStep(
- from = KeyguardState.AOD,
+ from = AOD,
to = KeyguardState.GONE,
value = 0.1f,
transitionState = TransitionState.RUNNING,
@@ -302,7 +307,7 @@ class KeyguardInteractorTest : SysuiTestCase() {
shadeRepository.setLegacyShadeExpansion(0f)
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.AOD,
+ from = AOD,
to = KeyguardState.GONE,
testScope,
)
@@ -324,8 +329,8 @@ class KeyguardInteractorTest : SysuiTestCase() {
shadeRepository.setLegacyShadeExpansion(0f)
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
+ from = AOD,
+ to = LOCKSCREEN,
testScope,
)
@@ -346,8 +351,8 @@ class KeyguardInteractorTest : SysuiTestCase() {
shadeRepository.setLegacyShadeExpansion(1f)
keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.AOD,
- to = KeyguardState.LOCKSCREEN,
+ from = AOD,
+ to = LOCKSCREEN,
testScope,
)
@@ -370,13 +375,13 @@ class KeyguardInteractorTest : SysuiTestCase() {
keyguardTransitionRepository.sendTransitionSteps(
listOf(
TransitionStep(
- from = KeyguardState.AOD,
+ from = AOD,
to = KeyguardState.GONE,
value = 0f,
- transitionState = TransitionState.STARTED,
+ transitionState = STARTED,
),
TransitionStep(
- from = KeyguardState.AOD,
+ from = AOD,
to = KeyguardState.GONE,
value = 0.1f,
transitionState = TransitionState.RUNNING,
@@ -468,4 +473,63 @@ class KeyguardInteractorTest : SysuiTestCase() {
runCurrent()
assertThat(isAnimate).isFalse()
}
+
+ @Test
+ @EnableSceneContainer
+ fun dozeAmount_updatedByAodTransitionWhenAodEnabled() =
+ testScope.runTest {
+ val dozeAmount by collectLastValue(underTest.dozeAmount)
+
+ keyguardRepository.setAodAvailable(true)
+
+ sendTransitionStep(TransitionStep(to = AOD, value = 0f, transitionState = STARTED))
+ assertThat(dozeAmount).isEqualTo(0f)
+
+ sendTransitionStep(TransitionStep(to = AOD, value = 0.5f, transitionState = RUNNING))
+ assertThat(dozeAmount).isEqualTo(0.5f)
+
+ sendTransitionStep(TransitionStep(to = AOD, value = 1f, transitionState = FINISHED))
+ assertThat(dozeAmount).isEqualTo(1f)
+
+ sendTransitionStep(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+ assertThat(dozeAmount).isEqualTo(1f)
+
+ sendTransitionStep(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+ assertThat(dozeAmount).isEqualTo(0.5f)
+
+ sendTransitionStep(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+ assertThat(dozeAmount).isEqualTo(0f)
+ }
+
+ @Test
+ @EnableSceneContainer
+ fun dozeAmount_updatedByDozeTransitionWhenAodDisabled() =
+ testScope.runTest {
+ val dozeAmount by collectLastValue(underTest.dozeAmount)
+
+ keyguardRepository.setAodAvailable(false)
+
+ sendTransitionStep(TransitionStep(to = DOZING, value = 0f, transitionState = STARTED))
+ assertThat(dozeAmount).isEqualTo(0f)
+
+ sendTransitionStep(TransitionStep(to = DOZING, value = 0.5f, transitionState = RUNNING))
+ assertThat(dozeAmount).isEqualTo(0.5f)
+
+ sendTransitionStep(TransitionStep(to = DOZING, value = 1f, transitionState = FINISHED))
+ assertThat(dozeAmount).isEqualTo(1f)
+
+ sendTransitionStep(TransitionStep(DOZING, LOCKSCREEN, 0f, STARTED))
+ assertThat(dozeAmount).isEqualTo(1f)
+
+ sendTransitionStep(TransitionStep(DOZING, LOCKSCREEN, 0.5f, RUNNING))
+ assertThat(dozeAmount).isEqualTo(0.5f)
+
+ sendTransitionStep(TransitionStep(DOZING, LOCKSCREEN, 1f, FINISHED))
+ assertThat(dozeAmount).isEqualTo(0f)
+ }
+
+ private suspend fun sendTransitionStep(step: TransitionStep) {
+ keyguardTransitionRepository.sendTransitionStep(step)
+ testScope.runCurrent()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractorTest.kt
new file mode 100644
index 000000000000..2558d583b001
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractorTest.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.domain.interactor
+
+import android.platform.test.annotations.EnableFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.policy.IKeyguardDismissCallback
+import com.android.internal.policy.IKeyguardStateCallback
+import com.android.keyguard.trustManager
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.dismissCallbackRegistry
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.domain.interactor.keyguardStateCallbackInteractor
+import com.android.systemui.testKosmos
+import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.time.fakeSystemClock
+import kotlin.test.Test
+import kotlinx.coroutines.test.currentTime
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mockito
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.kotlin.atLeast
+import org.mockito.kotlin.atLeastOnce
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.verify
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardStateCallbackInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var underTest: KeyguardStateCallbackInteractor
+ private lateinit var callback: IKeyguardStateCallback
+ private lateinit var systemClock: FakeSystemClock
+
+ @Before
+ fun setUp() {
+ systemClock = kosmos.fakeSystemClock
+ systemClock.setCurrentTimeMillis(testScope.currentTime)
+
+ underTest = kosmos.keyguardStateCallbackInteractor
+ underTest.start()
+
+ callback = mock<IKeyguardStateCallback>()
+ }
+
+ @Test
+ fun test_addCallback_passesInitialValues() =
+ testScope.runTest {
+ underTest.addCallback(callback)
+
+ verify(callback).onShowingStateChanged(anyBoolean(), anyInt())
+ verify(callback).onInputRestrictedStateChanged(anyBoolean())
+ verify(callback).onTrustedChanged(anyBoolean())
+ verify(callback).onSimSecureStateChanged(anyBoolean())
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun test_lockscreenVisibility_notifyDismissSucceeded_ifNotVisible() =
+ testScope.runTest {
+ underTest.addCallback(callback)
+
+ val dismissCallback = mock<IKeyguardDismissCallback>()
+ kosmos.dismissCallbackRegistry.addCallback(dismissCallback)
+ runCurrent()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope = testScope,
+ )
+
+ systemClock.advanceTime(1) // Required for DismissCallbackRegistry's bgExecutor
+ verify(dismissCallback).onDismissSucceeded()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = testScope,
+ )
+
+ Mockito.verifyNoMoreInteractions(dismissCallback)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
+ fun test_lockscreenVisibility_reportsKeyguardShowingChanged() =
+ testScope.runTest {
+ underTest.addCallback(callback)
+
+ Mockito.clearInvocations(callback)
+ Mockito.clearInvocations(kosmos.trustManager)
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GONE,
+ testScope = testScope,
+ )
+ runCurrent()
+
+ verify(callback, atLeastOnce()).onShowingStateChanged(eq(false), anyInt())
+ verify(kosmos.trustManager, atLeastOnce()).reportKeyguardShowingChanged()
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GONE,
+ to = KeyguardState.LOCKSCREEN,
+ testScope = testScope,
+ )
+
+ verify(callback, atLeastOnce()).onShowingStateChanged(eq(true), anyInt())
+ verify(kosmos.trustManager, atLeast(2)).reportKeyguardShowingChanged()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index 77106aec2fb4..a617484d7d94 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -1465,10 +1465,8 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
// WHEN the keyguard is occluded and device wakes up and is no longer dreaming
keyguardRepository.setDreaming(false)
- testScheduler.advanceTimeBy(150) // The dreaming signal is debounced.
- runCurrent()
keyguardRepository.setKeyguardOccluded(true)
- powerInteractor.setAwakeForTest()
+ testScheduler.advanceTimeBy(150) // The dreaming and occluded signals are debounced.
runCurrent()
// THEN a transition to OCCLUDED should occur
@@ -2059,12 +2057,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
fun glanceableHubToOccluded_communalKtfRefactor() =
testScope.runTest {
// GIVEN device is not dreaming
- powerInteractor.setAwakeForTest()
keyguardRepository.setDreaming(false)
- keyguardRepository.setDozeTransitionModel(
- DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
- )
- advanceTimeBy(600.milliseconds)
// GIVEN a prior transition has run to GLANCEABLE_HUB
communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
@@ -2073,6 +2066,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
// WHEN the keyguard is occluded
keyguardRepository.setKeyguardOccluded(true)
+ advanceTimeBy(200.milliseconds)
runCurrent()
assertThat(transitionRepository)
@@ -2218,6 +2212,7 @@ class KeyguardTransitionScenariosTest(flags: FlagsParameterization?) : SysuiTest
advanceTimeBy(10.milliseconds)
keyguardRepository.setKeyguardOccluded(true)
advanceTimeBy(200.milliseconds)
+ runCurrent()
assertThat(transitionRepository)
.startedTransition(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
index f31eb7f50405..309e3a8be14a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/UdfpsKeyguardInteractorTest.kt
@@ -27,11 +27,18 @@ import com.android.systemui.doze.util.BurnInHelperWrapper
import com.android.systemui.flags.andSceneContainer
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
import com.android.systemui.keyguard.shared.model.StatusBarState
+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.power.domain.interactor.PowerInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.PowerInteractorFactory
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.data.repository.fakeShadeRepository
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.domain.interactor.shadeLockscreenInteractor
@@ -62,6 +69,7 @@ class UdfpsKeyguardInteractorTest(flags: FlagsParameterization) : SysuiTestCase(
val kosmos = testKosmos()
val testScope = kosmos.testScope
val keyguardRepository = kosmos.fakeKeyguardRepository
+ val keyguardTransitionRepository by lazy { kosmos.fakeKeyguardTransitionRepository }
val shadeRepository = kosmos.fakeShadeRepository
val shadeTestUtil by lazy { kosmos.shadeTestUtil }
@@ -132,8 +140,15 @@ class UdfpsKeyguardInteractorTest(flags: FlagsParameterization) : SysuiTestCase(
assertThat(burnInOffsets?.x).isEqualTo(0)
// WHEN we're in the middle of the doze amount change
- keyguardRepository.setDozeAmount(.50f)
- runCurrent()
+ if (SceneContainerFlag.isEnabled) {
+ sendTransitionSteps(
+ TransitionStep(to = DOZING, value = 0.0f, transitionState = STARTED),
+ TransitionStep(to = DOZING, value = 0.5f, transitionState = RUNNING),
+ )
+ } else {
+ keyguardRepository.setDozeAmount(.50f)
+ runCurrent()
+ }
// THEN burn in is updated (between 0 and the full offset)
assertThat(burnInOffsets?.progress).isGreaterThan(0f)
@@ -144,8 +159,14 @@ class UdfpsKeyguardInteractorTest(flags: FlagsParameterization) : SysuiTestCase(
assertThat(burnInOffsets?.x).isLessThan(burnInXOffset)
// WHEN we're fully dozing
- keyguardRepository.setDozeAmount(1f)
- runCurrent()
+ if (SceneContainerFlag.isEnabled) {
+ sendTransitionSteps(
+ TransitionStep(to = DOZING, value = 1.0f, transitionState = FINISHED)
+ )
+ } else {
+ keyguardRepository.setDozeAmount(1f)
+ runCurrent()
+ }
// THEN burn in offsets are updated to final current values (for the given time)
assertThat(burnInOffsets?.progress).isEqualTo(burnInProgress)
@@ -217,7 +238,9 @@ class UdfpsKeyguardInteractorTest(flags: FlagsParameterization) : SysuiTestCase(
}
private fun setAwake() {
- keyguardRepository.setDozeAmount(0f)
+ if (!SceneContainerFlag.isEnabled) {
+ keyguardRepository.setDozeAmount(0f)
+ }
keyguardRepository.dozeTimeTick()
bouncerRepository.setAlternateVisible(false)
@@ -225,4 +248,11 @@ class UdfpsKeyguardInteractorTest(flags: FlagsParameterization) : SysuiTestCase(
bouncerRepository.setPrimaryShow(false)
powerInteractor.setAwakeForTest()
}
+
+ private suspend fun sendTransitionSteps(vararg steps: TransitionStep) {
+ steps.forEach { step ->
+ keyguardTransitionRepository.sendTransitionStep(step)
+ testScope.runCurrent()
+ }
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index f72a2e861be5..aefbc6b3b646 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -35,6 +35,7 @@ import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
@@ -107,6 +108,7 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest
uiEventLogger,
{ kosmos.interactionJankMonitor },
JavaAdapter(testScope.backgroundScope),
+ { kosmos.keyguardInteractor },
{ kosmos.keyguardTransitionInteractor },
{ kosmos.shadeInteractor },
{ kosmos.deviceUnlockedInteractor },
@@ -139,6 +141,7 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest
}
@Test
+ @DisableSceneContainer
fun testSetDozeAmountInternal_onlySetsOnce() {
val listener = mock(StatusBarStateController.StateListener::class.java)
underTest.addCallback(listener)
@@ -190,6 +193,7 @@ class StatusBarStateControllerImplTest(flags: FlagsParameterization) : SysuiTest
}
@Test
+ @DisableSceneContainer
fun testSetDozeAmount_immediatelyChangesDozeAmount_lockscreenTransitionFromAod() {
// Put controller in AOD state
underTest.setAndInstrumentDozeAmount(null, 1f, false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index 425f16ec7da1..4adf6936b5ab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -44,6 +44,7 @@ import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
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.DREAMING
import com.android.systemui.keyguard.shared.model.KeyguardState.GLANCEABLE_HUB
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
@@ -363,6 +364,69 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S
showLockscreen()
assertThat(alpha).isEqualTo(1f)
+ // Go to dozing
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = LOCKSCREEN,
+ to = DOZING,
+ testScope,
+ )
+ assertThat(alpha).isEqualTo(1f)
+
+ // Start transitioning to glanceable hub
+ val progress = 0.6f
+ kosmos.setTransition(
+ sceneTransition = Transition(from = Scenes.Lockscreen, to = Scenes.Communal),
+ stateTransition =
+ TransitionStep(
+ transitionState = TransitionState.STARTED,
+ from = DOZING,
+ to = GLANCEABLE_HUB,
+ value = 0f,
+ ),
+ )
+ runCurrent()
+ kosmos.setTransition(
+ sceneTransition =
+ Transition(
+ from = Scenes.Lockscreen,
+ to = Scenes.Communal,
+ progress = flowOf(progress),
+ ),
+ stateTransition =
+ TransitionStep(
+ transitionState = TransitionState.RUNNING,
+ from = DOZING,
+ to = GLANCEABLE_HUB,
+ value = progress,
+ ),
+ )
+ runCurrent()
+ // Keep notifications hidden during the transition from dream to hub
+ assertThat(alpha).isEqualTo(0)
+
+ // Finish transition to glanceable hub
+ kosmos.setTransition(
+ sceneTransition = Idle(Scenes.Communal),
+ stateTransition =
+ TransitionStep(
+ transitionState = TransitionState.FINISHED,
+ from = DOZING,
+ to = GLANCEABLE_HUB,
+ value = 1f,
+ ),
+ )
+ assertThat(alpha).isEqualTo(0f)
+ }
+
+ @Test
+ fun glanceableHubAlpha_dozingToHub() =
+ testScope.runTest {
+ val alpha by collectLastValue(underTest.glanceableHubAlpha)
+
+ // Start on lockscreen, notifications should be unhidden.
+ showLockscreen()
+ assertThat(alpha).isEqualTo(1f)
+
// Transition to dream, notifications should be hidden so that transition
// from dream->hub doesn't cause notification flicker.
showDream()
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
index 12597a7679fa..99c026cb028f 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardModel.kt
@@ -24,6 +24,7 @@ import android.text.TextUtils
import android.util.Log
import android.util.Size
import android.view.textclassifier.TextLinks
+import com.android.systemui.Flags.clipboardUseDescriptionMimetype
import com.android.systemui.res.R
import java.io.IOException
@@ -70,11 +71,11 @@ data class ClipboardModel(
context: Context,
utils: ClipboardOverlayUtils,
clipData: ClipData,
- source: String
+ source: String,
): ClipboardModel {
val sensitive = clipData.description?.extras?.getBoolean(EXTRA_IS_SENSITIVE) ?: false
val item = clipData.getItemAt(0)!!
- val type = getType(context, item)
+ val type = getType(context, item, clipData.description.getMimeType(0))
val remote = utils.isRemoteCopy(context, clipData, source)
return ClipboardModel(
clipData,
@@ -84,18 +85,26 @@ data class ClipboardModel(
item.textLinks,
item.uri,
sensitive,
- remote
+ remote,
)
}
- private fun getType(context: Context, item: ClipData.Item): Type {
+ private fun getType(context: Context, item: ClipData.Item, mimeType: String): Type {
return if (!TextUtils.isEmpty(item.text)) {
Type.TEXT
} else if (item.uri != null) {
- if (context.contentResolver.getType(item.uri)?.startsWith("image") == true) {
- Type.IMAGE
+ if (clipboardUseDescriptionMimetype()) {
+ if (mimeType.startsWith("image")) {
+ Type.IMAGE
+ } else {
+ Type.URI
+ }
} else {
- Type.URI
+ if (context.contentResolver.getType(item.uri)?.startsWith("image") == true) {
+ Type.IMAGE
+ } else {
+ Type.URI
+ }
}
} else {
Type.OTHER
@@ -107,6 +116,6 @@ data class ClipboardModel(
TEXT,
IMAGE,
URI,
- OTHER
+ OTHER,
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
index 04393feaae37..1bd541e1088a 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
@@ -65,7 +65,9 @@ constructor(
keyguardTransitionInteractor
.transitionValue(Scenes.Communal, KeyguardState.GLANCEABLE_HUB)
.map { it == 1f },
- not(keyguardInteractor.isDreaming),
+ // Use isDreamingAny because isDreaming is false in doze and doesn't change again
+ // when the screen turns on, which causes the dream to not start underneath the hub.
+ not(keyguardInteractor.isDreamingAny),
// TODO(b/362830856): Remove this workaround.
keyguardInteractor.isKeyguardShowing,
not(communalSceneInteractor.isLaunchingWidget),
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
index c7538bb4f696..905eda14e2d5 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneTransitionInteractor.kt
@@ -87,7 +87,9 @@ constructor(
*/
private val nextKeyguardStateInternal =
combine(
- keyguardInteractor.isAbleToDream,
+ // Don't use delayed dreaming signal as otherwise we might go to occluded or lock
+ // screen when closing hub if dream just started under the hub.
+ keyguardInteractor.isDreamingWithOverlay,
keyguardInteractor.isKeyguardOccluded,
keyguardInteractor.isKeyguardGoingAway,
keyguardInteractor.isKeyguardShowing,
@@ -156,7 +158,7 @@ constructor(
private suspend fun handleIdle(
prevTransition: ObservableTransitionState,
- idle: ObservableTransitionState.Idle
+ idle: ObservableTransitionState.Idle,
) {
if (
prevTransition is ObservableTransitionState.Transition &&
@@ -186,7 +188,7 @@ constructor(
internalTransitionInteractor.updateTransition(
currentTransitionId!!,
1f,
- TransitionState.FINISHED
+ TransitionState.FINISHED,
)
resetTransitionData()
}
@@ -204,7 +206,7 @@ constructor(
internalTransitionInteractor.updateTransition(
currentTransitionId!!,
1f,
- TransitionState.FINISHED
+ TransitionState.FINISHED,
)
resetTransitionData()
}
@@ -217,7 +219,7 @@ constructor(
private suspend fun handleTransition(
prevTransition: ObservableTransitionState,
- transition: ObservableTransitionState.Transition
+ transition: ObservableTransitionState.Transition,
) {
if (
prevTransition.isTransitioning(from = transition.fromContent, to = transition.toContent)
@@ -295,7 +297,7 @@ constructor(
internalTransitionInteractor.updateTransition(
currentTransitionId!!,
progress.coerceIn(0f, 1f),
- TransitionState.RUNNING
+ TransitionState.RUNNING,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index 28db3b861278..f90f02aad892 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -70,7 +70,14 @@ constructor(
) {
private val keyguardOccludedByApp: Flow<Boolean> =
if (KeyguardWmStateRefactor.isEnabled) {
- keyguardTransitionInteractor.currentKeyguardState.map { it == KeyguardState.OCCLUDED }
+ combine(
+ keyguardTransitionInteractor.currentKeyguardState,
+ communalSceneInteractor.isIdleOnCommunal,
+ ::Pair,
+ )
+ .map { (currentState, isIdleOnCommunal) ->
+ currentState == KeyguardState.OCCLUDED && !isIdleOnCommunal
+ }
} else {
combine(
keyguardInteractor.isKeyguardOccluded,
@@ -120,7 +127,7 @@ constructor(
// On fingerprint success when the screen is on and not dreaming, go to the home screen
fingerprintUnlockSuccessEvents
.sample(
- combine(powerInteractor.isInteractive, keyguardInteractor.isDreaming, ::Pair),
+ combine(powerInteractor.isInteractive, keyguardInteractor.isDreaming, ::Pair)
)
.collect { (interactive, dreaming) ->
if (interactive && !dreaming) {
@@ -148,7 +155,7 @@ constructor(
}
},
/* cancel= */ null,
- /* afterKeyguardGone */ false
+ /* afterKeyguardGone */ false,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index ba23eb341b89..0a38ce07a798 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -3314,7 +3314,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
setShowingLocked(false, "onKeyguardExitFinished: " + reason);
mWakeAndUnlocking = false;
- mDismissCallbackRegistry.notifyDismissSucceeded();
+
+ if (!KeyguardWmStateRefactor.isEnabled()) {
+ mDismissCallbackRegistry.notifyDismissSucceeded();
+ }
+
resetKeyguardDonePendingLocked();
mHideAnimationRun = false;
adjustStatusBarLocked();
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 49303e089036..130242f55600 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
@@ -58,6 +58,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.mapLatest
@@ -486,19 +487,34 @@ constructor(
override val isDreaming: MutableStateFlow<Boolean> = MutableStateFlow(false)
- override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow {
- val callback =
- object : StatusBarStateController.StateListener {
- override fun onDozeAmountChanged(linear: Float, eased: Float) {
- trySendWithFailureLogging(linear, TAG, "updated dozeAmount")
- }
- }
+ private val _preSceneLinearDozeAmount: Flow<Float> =
+ if (SceneContainerFlag.isEnabled) {
+ emptyFlow()
+ } else {
+ conflatedCallbackFlow {
+ val callback =
+ object : StatusBarStateController.StateListener {
+ override fun onDozeAmountChanged(linear: Float, eased: Float) {
+ trySendWithFailureLogging(linear, TAG, "updated dozeAmount")
+ }
+ }
- statusBarStateController.addCallback(callback)
- trySendWithFailureLogging(statusBarStateController.dozeAmount, TAG, "initial dozeAmount")
+ statusBarStateController.addCallback(callback)
+ trySendWithFailureLogging(
+ statusBarStateController.dozeAmount,
+ TAG,
+ "initial dozeAmount"
+ )
- awaitClose { statusBarStateController.removeCallback(callback) }
- }
+ awaitClose { statusBarStateController.removeCallback(callback) }
+ }
+ }
+
+ override val linearDozeAmount: Flow<Float>
+ get() {
+ SceneContainerFlag.assertInLegacyMode()
+ return _preSceneLinearDozeAmount
+ }
override val dozeTransitionModel: Flow<DozeTransitionModel> = conflatedCallbackFlow {
val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index b0820a747e17..8c7fe5f87a3f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -150,7 +150,9 @@ constructor(
if (!SceneContainerFlag.isEnabled) {
startTransitionTo(KeyguardState.GLANCEABLE_HUB)
}
- } else if (isCommunalAvailable && dreamManager.canStartDreaming(true)) {
+ } else if (isCommunalAvailable && dreamManager.canStartDreaming(false)) {
+ // Using false for isScreenOn as canStartDreaming returns false if any
+ // dream, including doze, is active.
// This case handles tapping the power button to transition through
// dream -> off -> hub.
if (!SceneContainerFlag.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 2434b29c0cdd..9a0a85823929 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -17,9 +17,12 @@
package com.android.systemui.keyguard.domain.interactor
import android.animation.ValueAnimator
+import android.annotation.SuppressLint
+import android.app.DreamManager
import com.android.app.animation.Interpolators
import com.android.app.tracing.coroutines.launch
import com.android.systemui.Flags.communalSceneKtfRefactor
+import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -60,10 +63,12 @@ constructor(
@Main mainDispatcher: CoroutineDispatcher,
keyguardInteractor: KeyguardInteractor,
private val glanceableHubTransitions: GlanceableHubTransitions,
+ private val communalInteractor: CommunalInteractor,
private val communalSceneInteractor: CommunalSceneInteractor,
private val communalSettingsInteractor: CommunalSettingsInteractor,
powerInteractor: PowerInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+ private val dreamManager: DreamManager,
private val deviceEntryInteractor: DeviceEntryInteractor,
) :
TransitionInteractor(
@@ -76,6 +81,7 @@ constructor(
keyguardInteractor = keyguardInteractor,
) {
+ @SuppressLint("MissingPermission")
override fun start() {
listenForDreamingToAlternateBouncer()
listenForDreamingToOccluded()
@@ -86,6 +92,8 @@ constructor(
listenForTransitionToCamera(scope, keyguardInteractor)
if (!communalSceneKtfRefactor()) {
listenForDreamingToGlanceableHub()
+ } else {
+ listenForDreamingToGlanceableHubFromPowerButton()
}
listenForDreamingToPrimaryBouncer()
}
@@ -112,6 +120,34 @@ constructor(
}
}
+ /**
+ * Normally when pressing power button from the dream, the devices goes from DREAMING to DOZING,
+ * then [FromDozingTransitionInteractor] handles the transition to GLANCEABLE_HUB. However if
+ * the power button is pressed quickly, we may need to go directly from DREAMING to
+ * GLANCEABLE_HUB as the transition to DOZING has not occurred yet.
+ */
+ @SuppressLint("MissingPermission")
+ private fun listenForDreamingToGlanceableHubFromPowerButton() {
+ if (!communalSettingsInteractor.isCommunalFlagEnabled()) return
+ if (SceneContainerFlag.isEnabled) return
+ scope.launch {
+ powerInteractor.isAwake
+ .debounce(50L)
+ .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
+ .sample(communalInteractor.isCommunalAvailable)
+ .collect { isCommunalAvailable ->
+ if (isCommunalAvailable && dreamManager.canStartDreaming(false)) {
+ // This case handles tapping the power button to transition through
+ // dream -> off -> hub.
+ communalSceneInteractor.snapToScene(
+ newScene = CommunalScenes.Communal,
+ loggingReason = "from dreaming to hub",
+ )
+ }
+ }
+ }
+ }
+
private fun listenForDreamingToPrimaryBouncer() {
// TODO(b/336576536): Check if adaptation for scene framework is needed
if (SceneContainerFlag.isEnabled) return
@@ -144,7 +180,7 @@ constructor(
} else {
startTransitionTo(
KeyguardState.LOCKSCREEN,
- ownerReason = "Dream has ended and device is awake"
+ ownerReason = "Dream has ended and device is awake",
)
}
}
@@ -158,15 +194,14 @@ constructor(
scope.launch {
combine(
keyguardInteractor.isKeyguardOccluded,
- keyguardInteractor.isAbleToDream
- // Debounce the dreaming signal since there is a race condition between
- // the occluded and dreaming signals. We therefore add a small delay
- // to give enough time for occluded to flip to false when the dream
- // ends, to avoid transitioning to OCCLUDED erroneously when exiting
- // the dream.
- .debounce(100.milliseconds),
- ::Pair
+ keyguardInteractor.isDreaming,
+ ::Pair,
)
+ // Debounce signals since there is a race condition between the occluded and
+ // dreaming signals when starting or stopping dreaming. We therefore add a small
+ // delay to give enough time for occluded to flip to false when the dream
+ // ends, to avoid transitioning to OCCLUDED erroneously when exiting the dream.
+ .debounce(100.milliseconds)
.filterRelevantKeyguardStateAnd { (isOccluded, isDreaming) ->
isOccluded && !isDreaming
}
@@ -194,12 +229,12 @@ constructor(
if (dismissable) {
startTransitionTo(
KeyguardState.GONE,
- ownerReason = "No longer dreaming; dismissable"
+ ownerReason = "No longer dreaming; dismissable",
)
} else {
startTransitionTo(
KeyguardState.LOCKSCREEN,
- ownerReason = "No longer dreaming"
+ ownerReason = "No longer dreaming",
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index 7759298cb32a..6b6a3dce630a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -202,15 +202,15 @@ constructor(
scope.launch {
combine(
keyguardInteractor.isKeyguardOccluded,
- keyguardInteractor.isAbleToDream
- // Debounce the dreaming signal since there is a race condition between
- // the occluded and dreaming signals. We therefore add a small delay
- // to give enough time for occluded to flip to false when the dream
- // ends, to avoid transitioning to OCCLUDED erroneously when exiting
- // the dream.
- .debounce(100.milliseconds),
+ keyguardInteractor.isDreaming,
::Pair,
)
+ // Debounce signals since there is a race condition between the occluded and
+ // dreaming signals when starting or stopping dreaming. We therefore add a small
+ // delay to give enough time for occluded to flip to false when the dream
+ // ends, to avoid transitioning to OCCLUDED erroneously when exiting the dream
+ // or when the dream starts underneath the hub.
+ .debounce(200.milliseconds)
.sampleFilter(
// When launching activities from widgets on the hub, we have a
// custom occlusion animation.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
index 4457f1dfaf09..9b9bdd1bde9b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
@@ -23,12 +23,10 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.DismissCallbackRegistry
-import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.keyguard.shared.model.DismissAction
import com.android.systemui.keyguard.shared.model.KeyguardDone
-import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
@@ -37,7 +35,6 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map
@@ -59,7 +56,6 @@ constructor(
trustRepository: TrustRepository,
alternateBouncerInteractor: AlternateBouncerInteractor,
powerInteractor: PowerInteractor,
- keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
/*
* Updates when a biometric has authenticated the device and is requesting to dismiss
@@ -165,14 +161,4 @@ constructor(
}
}
}
-
- init {
- if (KeyguardWmStateRefactor.isEnabled) {
- scope.launch {
- keyguardTransitionInteractor.currentKeyguardState
- .filter { it == KeyguardState.GONE }
- .collect { dismissCallbackRegistry.notifyDismissSucceeded() }
- }
- }
- }
}
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 e444092bd175..e6ee11215595 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
@@ -140,12 +140,6 @@ constructor(
_notificationPlaceholderBounds.value = position
}
- /**
- * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
- * all.
- */
- val dozeAmount: Flow<Float> = repository.linearDozeAmount
-
/** Whether the system is in doze mode. */
val isDozing: StateFlow<Boolean> = repository.isDozing
@@ -155,6 +149,23 @@ constructor(
/** Whether Always-on Display mode is available. */
val isAodAvailable: StateFlow<Boolean> = repository.isAodAvailable
+ /**
+ * The amount of doze the system is in, where `1.0` is fully dozing and `0.0` is not dozing at
+ * all.
+ */
+ val dozeAmount: Flow<Float> =
+ if (SceneContainerFlag.isEnabled) {
+ isAodAvailable.flatMapLatest { isAodAvailable ->
+ if (isAodAvailable) {
+ keyguardTransitionInteractor.transitionValue(AOD)
+ } else {
+ keyguardTransitionInteractor.transitionValue(DOZING)
+ }
+ }
+ } else {
+ repository.linearDozeAmount
+ }
+
/** Doze transition information. */
val dozeTransitionModel: Flow<DozeTransitionModel> = repository.dozeTransitionModel
@@ -164,8 +175,8 @@ constructor(
val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
/**
- * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true,
- * but not vice-versa. Also accounts for [isDreamingWithOverlay]
+ * Whether the system is dreaming. [KeyguardRepository.isDreaming] will be always be true when
+ * [isDozing] is true, but not vice-versa. Also accounts for [isDreamingWithOverlay].
*/
val isDreaming: StateFlow<Boolean> =
merge(repository.isDreaming, repository.isDreamingWithOverlay)
@@ -175,6 +186,9 @@ constructor(
initialValue = false,
)
+ /** Whether any dreaming is running, including the doze dream. */
+ val isDreamingAny: Flow<Boolean> = repository.isDreaming
+
/** Whether the system is dreaming and the active dream is hosted in lockscreen */
val isActiveDreamLockscreenHosted: StateFlow<Boolean> = repository.isActiveDreamLockscreenHosted
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt
index 7fd348b8b40e..6fe4ff5122d0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardStateCallbackInteractor.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.domain.interactor
+import android.app.trust.TrustManager
import android.os.DeadObjectException
import android.os.RemoteException
import com.android.internal.policy.IKeyguardStateCallback
@@ -24,6 +25,7 @@ import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.KeyguardWmStateRefactor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.scene.shared.flag.SceneContainerFlag
@@ -32,7 +34,6 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -53,6 +54,9 @@ constructor(
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val trustInteractor: TrustInteractor,
private val simBouncerInteractor: SimBouncerInteractor,
+ private val dismissCallbackRegistry: DismissCallbackRegistry,
+ private val wmLockscreenVisibilityInteractor: WindowManagerLockscreenVisibilityInteractor,
+ private val trustManager: TrustManager,
) : CoreStartable {
private val callbacks = mutableListOf<IKeyguardStateCallback>()
@@ -62,28 +66,31 @@ constructor(
}
applicationScope.launch {
- combine(
- selectedUserInteractor.selectedUser,
- keyguardTransitionInteractor.currentKeyguardState,
- keyguardTransitionInteractor.startedKeyguardTransitionStep,
- ::Triple,
- )
- .collectLatest { (selectedUser, _, _) ->
- val iterator = callbacks.iterator()
- withContext(backgroundDispatcher) {
- while (iterator.hasNext()) {
- val callback = iterator.next()
- try {
- callback.onShowingStateChanged(!isIdleInGone(), selectedUser)
- callback.onInputRestrictedStateChanged(!isIdleInGone())
- } catch (e: RemoteException) {
- if (e is DeadObjectException) {
- iterator.remove()
- }
+ wmLockscreenVisibilityInteractor.lockscreenVisibility.collectLatest { visible ->
+ val iterator = callbacks.iterator()
+ withContext(backgroundDispatcher) {
+ while (iterator.hasNext()) {
+ val callback = iterator.next()
+ try {
+ callback.onShowingStateChanged(
+ visible,
+ selectedUserInteractor.getSelectedUserId(),
+ )
+ callback.onInputRestrictedStateChanged(visible)
+
+ trustManager.reportKeyguardShowingChanged()
+
+ if (!visible) {
+ dismissCallbackRegistry.notifyDismissSucceeded()
+ }
+ } catch (e: RemoteException) {
+ if (e is DeadObjectException) {
+ iterator.remove()
}
}
}
}
+ }
}
applicationScope.launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt
index aee34e1e713b..1e42e196bbc7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModel.kt
@@ -26,6 +26,7 @@ import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
import com.android.systemui.scene.shared.model.Scenes
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
@SysUISingleton
class DozingToGlanceableHubTransitionViewModel
@@ -35,10 +36,16 @@ constructor(animationFlow: KeyguardTransitionAnimationFlow) : DeviceEntryIconTra
animationFlow
.setup(
duration = TO_GLANCEABLE_HUB_DURATION,
- edge = Edge.create(DOZING, Scenes.Communal)
+ edge = Edge.create(DOZING, Scenes.Communal),
)
.setupWithoutSceneContainer(edge = Edge.create(DOZING, GLANCEABLE_HUB))
override val deviceEntryParentViewAlpha: Flow<Float> =
transitionAnimation.immediatelyTransitionTo(1f)
+
+ /**
+ * Hide notifications when transitioning directly from dozing to hub, such as when pressing
+ * power button when dozing and docked.
+ */
+ val notificationAlpha: Flow<Float> = flowOf(0f)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 4b62eab08775..0d55709e94d6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -179,14 +179,6 @@ constructor(
} else {
button(KeyguardQuickAffordancePosition.BOTTOM_START)
}
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue =
- KeyguardQuickAffordanceViewModel(
- slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId()
- ),
- )
/** An observable for the view-model of the "end button" quick affordance. */
val endButton: Flow<KeyguardQuickAffordanceViewModel> =
@@ -200,14 +192,6 @@ constructor(
} else {
button(KeyguardQuickAffordancePosition.BOTTOM_END)
}
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue =
- KeyguardQuickAffordanceViewModel(
- slotId = KeyguardQuickAffordancePosition.BOTTOM_END.toSlotId()
- ),
- )
/**
* Notifies that a slot with the given ID has been selected in the preview experience that is
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index b3c697e06a92..1216a8879751 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -551,6 +551,12 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
}
@Override
+ public void appTransitionStarting(int displayId, long startTime, long duration,
+ boolean forced) {
+ appTransitionPending(false);
+ }
+
+ @Override
public void appTransitionCancelled(int displayId) {
appTransitionPending(false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 5eef8ea1999d..769abafed69f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -207,8 +207,8 @@ public class KeyguardIndicationController {
protected boolean mPowerPluggedInWireless;
protected boolean mPowerPluggedInDock;
protected int mChargingSpeed;
+ protected boolean mPowerCharged;
- private boolean mPowerCharged;
/** Whether the battery defender is triggered. */
private boolean mBatteryDefender;
/** Whether the battery defender is triggered with the device plugged. */
@@ -1100,14 +1100,15 @@ public class KeyguardIndicationController {
String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
return mContext.getResources().getString(
R.string.keyguard_plugged_in_incompatible_charger, percentage);
- } else if (mPowerCharged) {
- return mContext.getResources().getString(R.string.keyguard_charged);
}
return computePowerChargingStringIndication();
}
protected String computePowerChargingStringIndication() {
+ if (mPowerCharged) {
+ return mContext.getResources().getString(R.string.keyguard_charged);
+ }
final boolean hasChargingTime = mChargingTimeRemaining > 0;
int chargingId;
if (mPowerPluggedInWired) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index 7f5551274d55..8a6ec2aa27c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -50,8 +50,8 @@ import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteract
import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus;
import com.android.systemui.keyguard.MigrateClocksToBlueprint;
import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.shared.model.KeyguardState;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.res.R;
import com.android.systemui.scene.data.model.SceneStack;
@@ -115,6 +115,7 @@ public class StatusBarStateControllerImpl implements
private final UiEventLogger mUiEventLogger;
private final Lazy<InteractionJankMonitor> mInteractionJankMonitorLazy;
private final JavaAdapter mJavaAdapter;
+ private final Lazy<KeyguardInteractor> mKeyguardInteractorLazy;
private final Lazy<KeyguardTransitionInteractor> mKeyguardTransitionInteractorLazy;
private final Lazy<ShadeInteractor> mShadeInteractorLazy;
private final Lazy<DeviceUnlockedInteractor> mDeviceUnlockedInteractorLazy;
@@ -185,6 +186,7 @@ public class StatusBarStateControllerImpl implements
UiEventLogger uiEventLogger,
Lazy<InteractionJankMonitor> interactionJankMonitorLazy,
JavaAdapter javaAdapter,
+ Lazy<KeyguardInteractor> keyguardInteractor,
Lazy<KeyguardTransitionInteractor> keyguardTransitionInteractor,
Lazy<ShadeInteractor> shadeInteractorLazy,
Lazy<DeviceUnlockedInteractor> deviceUnlockedInteractorLazy,
@@ -195,6 +197,7 @@ public class StatusBarStateControllerImpl implements
mUiEventLogger = uiEventLogger;
mInteractionJankMonitorLazy = interactionJankMonitorLazy;
mJavaAdapter = javaAdapter;
+ mKeyguardInteractorLazy = keyguardInteractor;
mKeyguardTransitionInteractorLazy = keyguardTransitionInteractor;
mShadeInteractorLazy = shadeInteractorLazy;
mDeviceUnlockedInteractorLazy = deviceUnlockedInteractorLazy;
@@ -233,8 +236,8 @@ public class StatusBarStateControllerImpl implements
this::onStatusBarStateChanged);
mJavaAdapter.alwaysCollectFlow(
- mKeyguardTransitionInteractorLazy.get().transitionValue(KeyguardState.AOD),
- this::onAodKeyguardStateTransitionValueChanged);
+ mKeyguardInteractorLazy.get().getDozeAmount(),
+ this::setDozeAmountInternal);
}
}
@@ -404,6 +407,7 @@ public class StatusBarStateControllerImpl implements
@Override
public void setAndInstrumentDozeAmount(View view, float dozeAmount, boolean animated) {
+ SceneContainerFlag.assertInLegacyMode();
if (mDarkAnimator != null && mDarkAnimator.isRunning()) {
if (animated && mDozeAmountTarget == dozeAmount) {
return;
@@ -439,6 +443,7 @@ public class StatusBarStateControllerImpl implements
}
private void startDozeAnimation() {
+ SceneContainerFlag.assertInLegacyMode();
if (mDozeAmount == 0f || mDozeAmount == 1f) {
mDozeInterpolator = mIsDozing
? Interpolators.FAST_OUT_SLOW_IN
@@ -457,6 +462,7 @@ public class StatusBarStateControllerImpl implements
@VisibleForTesting
protected ObjectAnimator createDarkAnimator() {
+ SceneContainerFlag.assertInLegacyMode();
ObjectAnimator darkAnimator = ObjectAnimator.ofFloat(
this, SET_DARK_AMOUNT_PROPERTY, mDozeAmountTarget);
darkAnimator.setInterpolator(Interpolators.LINEAR);
@@ -710,14 +716,6 @@ public class StatusBarStateControllerImpl implements
updateStateAndNotifyListeners(newState);
}
- private void onAodKeyguardStateTransitionValueChanged(float value) {
- if (SceneContainerFlag.isUnexpectedlyInLegacyMode()) {
- return;
- }
-
- setDozeAmountInternal(value);
- }
-
private static final Map<SceneKey, Integer> sStatusBarStateByLockedSceneKey = Map.of(
Scenes.Lockscreen, StatusBarState.KEYGUARD,
Scenes.Bouncer, StatusBarState.KEYGUARD,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 560028cb5640..7b6a2cb62b14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -444,11 +444,9 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView
if (onFinishedRunnable != null) {
onFinishedRunnable.run();
}
- if (mRunWithoutInterruptions) {
- enableAppearDrawing(false);
- }
// We need to reset the View state, even if the animation was cancelled
+ enableAppearDrawing(false);
onAppearAnimationFinished(isAppearing);
if (mRunWithoutInterruptions) {
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 e34eb61c5cbd..8ca26bea9705 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
@@ -45,6 +45,7 @@ import com.android.systemui.keyguard.ui.viewmodel.AodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.AodToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.DozingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DozingToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
@@ -112,6 +113,7 @@ constructor(
private val aodToGoneTransitionViewModel: AodToGoneTransitionViewModel,
private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
private val aodToOccludedTransitionViewModel: AodToOccludedTransitionViewModel,
+ dozingToGlanceableHubTransitionViewModel: DozingToGlanceableHubTransitionViewModel,
private val dozingToLockscreenTransitionViewModel: DozingToLockscreenTransitionViewModel,
private val dozingToOccludedTransitionViewModel: DozingToOccludedTransitionViewModel,
private val dreamingToLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
@@ -506,6 +508,7 @@ constructor(
merge(
lockscreenToGlanceableHubTransitionViewModel.notificationAlpha,
glanceableHubToLockscreenTransitionViewModel.notificationAlpha,
+ dozingToGlanceableHubTransitionViewModel.notificationAlpha,
)
// Manually emit on start because [notificationAlpha] only starts emitting
// when transitions start.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 68163b28dd09..4ef328cf1623 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -149,7 +149,7 @@ class MobileIconInteractorImpl(
override val isForceHidden: Flow<Boolean>,
connectionRepository: MobileConnectionRepository,
private val context: Context,
- val carrierIdOverrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
+ val carrierIdOverrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl(),
) : MobileIconInteractor {
override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer
@@ -182,7 +182,7 @@ class MobileIconInteractorImpl(
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- connectionRepository.networkName.value
+ connectionRepository.networkName.value,
)
override val carrierName =
@@ -198,7 +198,7 @@ class MobileIconInteractorImpl(
.stateIn(
scope,
SharingStarted.WhileSubscribed(),
- connectionRepository.carrierName.value.name
+ connectionRepository.carrierName.value.name,
)
/** What the mobile icon would be before carrierId overrides */
@@ -219,10 +219,7 @@ class MobileIconInteractorImpl(
.stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
override val networkTypeIconGroup =
- combine(
- defaultNetworkType,
- carrierIdIconOverrideExists,
- ) { networkType, overrideExists ->
+ combine(defaultNetworkType, carrierIdIconOverrideExists) { networkType, overrideExists ->
// DefaultIcon comes out of the icongroup lookup, we check for overrides here
if (overrideExists) {
val iconOverride =
@@ -316,30 +313,30 @@ class MobileIconInteractorImpl(
/** Whether or not to show the error state of [SignalDrawable] */
private val showExclamationMark: StateFlow<Boolean> =
- combine(
- defaultSubscriptionHasDataEnabled,
+ combine(defaultSubscriptionHasDataEnabled, isDefaultConnectionFailed, isInService) {
+ isDefaultDataEnabled,
isDefaultConnectionFailed,
- isInService,
- ) { isDefaultDataEnabled, isDefaultConnectionFailed, isInService ->
+ isInService ->
!isDefaultDataEnabled || isDefaultConnectionFailed || !isInService
}
.stateIn(scope, SharingStarted.WhileSubscribed(), true)
private val cellularShownLevel: StateFlow<Int> =
- combine(
+ combine(level, isInService, connectionRepository.inflateSignalStrength) {
level,
isInService,
- connectionRepository.inflateSignalStrength,
- ) { level, isInService, inflate ->
+ inflate ->
if (isInService) {
if (inflate) level + 1 else level
} else 0
}
.stateIn(scope, SharingStarted.WhileSubscribed(), 0)
- // Satellite level is unaffected by the isInService or inflateSignalStrength properties
+ // Satellite level is unaffected by the inflateSignalStrength property
// See b/346904529 for details
- private val satelliteShownLevel: StateFlow<Int> = level
+ private val satelliteShownLevel: StateFlow<Int> =
+ combine(level, isInService) { level, isInService -> if (isInService) level else 0 }
+ .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
private val cellularIcon: Flow<SignalIconModel.Cellular> =
combine(
@@ -362,7 +359,7 @@ class MobileIconInteractorImpl(
level = it,
icon =
SatelliteIconModel.fromSignalStrength(it)
- ?: SatelliteIconModel.fromSignalStrength(0)!!
+ ?: SatelliteIconModel.fromSignalStrength(0)!!,
)
}
@@ -383,11 +380,7 @@ class MobileIconInteractorImpl(
}
}
.distinctUntilChanged()
- .logDiffsForTable(
- tableLogBuffer,
- columnPrefix = "icon",
- initialValue = initial,
- )
+ .logDiffsForTable(tableLogBuffer, columnPrefix = "icon", initialValue = initial)
.stateIn(scope, SharingStarted.WhileSubscribed(), initial)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
index 4cb66c19a0bb..eea4c212e40e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt
@@ -89,19 +89,30 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa
launch {
viewModel.primaryOngoingActivityChip.collect { primaryChipModel ->
OngoingActivityChipBinder.bind(primaryChipModel, primaryChipView)
- when (primaryChipModel) {
- is OngoingActivityChipModel.Shown ->
- listener.onOngoingActivityStatusChanged(
- hasPrimaryOngoingActivity = true,
- hasSecondaryOngoingActivity = false,
- shouldAnimate = true,
- )
- is OngoingActivityChipModel.Hidden ->
- listener.onOngoingActivityStatusChanged(
- hasPrimaryOngoingActivity = false,
- hasSecondaryOngoingActivity = false,
- shouldAnimate = primaryChipModel.shouldAnimate,
- )
+ if (StatusBarSimpleFragment.isEnabled) {
+ when (primaryChipModel) {
+ is OngoingActivityChipModel.Shown ->
+ primaryChipView.show(shouldAnimateChange = true)
+ is OngoingActivityChipModel.Hidden ->
+ primaryChipView.hide(
+ shouldAnimateChange = primaryChipModel.shouldAnimate
+ )
+ }
+ } else {
+ when (primaryChipModel) {
+ is OngoingActivityChipModel.Shown ->
+ listener.onOngoingActivityStatusChanged(
+ hasPrimaryOngoingActivity = true,
+ hasSecondaryOngoingActivity = false,
+ shouldAnimate = true,
+ )
+ is OngoingActivityChipModel.Hidden ->
+ listener.onOngoingActivityStatusChanged(
+ hasPrimaryOngoingActivity = false,
+ hasSecondaryOngoingActivity = false,
+ shouldAnimate = primaryChipModel.shouldAnimate,
+ )
+ }
}
}
}
@@ -118,14 +129,22 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa
// TODO(b/364653005): Don't show the secondary chip if there isn't
// enough space for it.
OngoingActivityChipBinder.bind(chips.secondary, secondaryChipView)
- listener.onOngoingActivityStatusChanged(
- hasPrimaryOngoingActivity =
- chips.primary is OngoingActivityChipModel.Shown,
- hasSecondaryOngoingActivity =
- chips.secondary is OngoingActivityChipModel.Shown,
- // TODO(b/364653005): Figure out the animation story here.
- shouldAnimate = true,
- )
+
+ if (StatusBarSimpleFragment.isEnabled) {
+ primaryChipView.adjustVisibility(chips.primary.toVisibilityModel())
+ secondaryChipView.adjustVisibility(
+ chips.secondary.toVisibilityModel()
+ )
+ } else {
+ listener.onOngoingActivityStatusChanged(
+ hasPrimaryOngoingActivity =
+ chips.primary is OngoingActivityChipModel.Shown,
+ hasSecondaryOngoingActivity =
+ chips.secondary is OngoingActivityChipModel.Shown,
+ // TODO(b/364653005): Figure out the animation story here.
+ shouldAnimate = true,
+ )
+ }
}
}
}
@@ -164,6 +183,15 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa
}
}
+ private fun OngoingActivityChipModel.toVisibilityModel():
+ CollapsedStatusBarViewModel.VisibilityModel {
+ return CollapsedStatusBarViewModel.VisibilityModel(
+ visibility = if (this is OngoingActivityChipModel.Shown) View.VISIBLE else View.GONE,
+ // TODO(b/364653005): Figure out the animation story here.
+ shouldAnimateChange = true,
+ )
+ }
+
private fun animateLightsOutView(view: View, visible: Boolean) {
view.animate().cancel()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
index 692e0e4f55f8..366ea3516965 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModel.kt
@@ -96,8 +96,6 @@ interface CollapsedStatusBarViewModel {
val isNotificationIconContainerVisible: Flow<VisibilityModel>
val isSystemInfoVisible: Flow<VisibilityModel>
- // TODO(b/364360986): Add isOngoingActivityChipVisible: Flow<VisibilityModel>
-
/**
* Apps can request a low profile mode [android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE] where
* status bar and navigation icons dim. In this mode, a notification dot appears where the
@@ -211,7 +209,7 @@ constructor(
isStatusBarAllowed && visibilityViaDisableFlags.areNotificationIconsAllowed
VisibilityModel(
showNotificationIconContainer.toVisibilityInt(),
- visibilityViaDisableFlags.animate
+ visibilityViaDisableFlags.animate,
)
}
override val isSystemInfoVisible: Flow<VisibilityModel> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
index 27bc6d36c1e6..76389f39e484 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTile.kt
@@ -71,7 +71,7 @@ fun ModeTile(viewModel: ModeTileViewModel) {
.semantics { stateDescription = viewModel.stateDescription },
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement =
- Arrangement.spacedBy(space = 8.dp, alignment = Alignment.Start),
+ Arrangement.spacedBy(space = 12.dp, alignment = Alignment.Start),
) {
Icon(icon = viewModel.icon, modifier = Modifier.size(24.dp))
Column {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
index 5953ea598929..5392e38823c6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/composable/ModeTileGrid.kt
@@ -26,7 +26,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.systemui.Flags
import com.android.systemui.statusbar.policy.ui.dialog.viewmodel.ModesDialogViewModel
@Composable
@@ -34,8 +33,8 @@ fun ModeTileGrid(viewModel: ModesDialogViewModel) {
val tiles by viewModel.tiles.collectAsStateWithLifecycle(initialValue = emptyList())
LazyVerticalGrid(
- columns = GridCells.Fixed(if (Flags.modesDialogSingleRows()) 1 else 2),
- modifier = Modifier.fillMaxWidth().heightIn(max = 300.dp),
+ columns = GridCells.Fixed(1),
+ modifier = Modifier.fillMaxWidth().heightIn(max = 320.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index d63e728cf443..d63e728cf443 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index e444db4895d4..e444db4895d4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
index 7bb6ef1c8895..7bb6ef1c8895 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
index 5d76e325bd3a..85e8ab43b2ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardModelTest.kt
@@ -22,10 +22,12 @@ import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import android.os.PersistableBundle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE
import com.android.systemui.SysuiTestCase
-import com.android.systemui.util.mockito.whenever
import java.io.IOException
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -37,6 +39,7 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.any
import org.mockito.Mock
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -88,7 +91,8 @@ class ClipboardModelTest : SysuiTestCase() {
@Test
@Throws(IOException::class)
- fun test_imageClipData() {
+ @DisableFlags(FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE)
+ fun test_imageClipData_legacy() {
val testBitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888)
whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
whenever(mMockContext.resources).thenReturn(mContext.resources)
@@ -103,6 +107,21 @@ class ClipboardModelTest : SysuiTestCase() {
@Test
@Throws(IOException::class)
+ @EnableFlags(FLAG_CLIPBOARD_USE_DESCRIPTION_MIMETYPE)
+ fun test_imageClipData() {
+ val testBitmap = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888)
+ whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
+ whenever(mMockContext.resources).thenReturn(mContext.resources)
+ whenever(mMockContentResolver.loadThumbnail(any(), any(), any())).thenReturn(testBitmap)
+ whenever(mMockContentResolver.getType(any())).thenReturn("text")
+ val imageClipData = ClipData("Test", arrayOf("image/png"), ClipData.Item(Uri.parse("test")))
+ val model = ClipboardModel.fromClipData(mMockContext, mClipboardUtils, imageClipData, "")
+ assertEquals(ClipboardModel.Type.IMAGE, model.type)
+ assertEquals(testBitmap, model.loadThumbnail(mMockContext))
+ }
+
+ @Test
+ @Throws(IOException::class)
fun test_imageClipData_loadFailure() {
whenever(mMockContext.contentResolver).thenReturn(mMockContentResolver)
whenever(mMockContext.resources).thenReturn(mContext.resources)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
index 414974cc2941..414974cc2941 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/interactor/MediaCarouselInteractorTest.kt
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 4bd0c757543b..a6afd0e499f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -453,6 +453,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mUiEventLogger,
() -> mKosmos.getInteractionJankMonitor(),
mJavaAdapter,
+ () -> mKeyguardInteractor,
() -> mKeyguardTransitionInteractor,
() -> mShadeInteractor,
() -> mKosmos.getDeviceUnlockedInteractor(),
@@ -611,6 +612,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
new UiEventLoggerFake(),
() -> mKosmos.getInteractionJankMonitor(),
mJavaAdapter,
+ () -> mKeyguardInteractor,
() -> mKeyguardTransitionInteractor,
() -> mShadeInteractor,
() -> mKosmos.getDeviceUnlockedInteractor(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 4fd830d0891e..a8bcfbcfc539 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -756,6 +756,27 @@ class MobileIconInteractorTest : SysuiTestCase() {
assertThat(latest!!.level).isEqualTo(4)
}
+ @EnableFlags(com.android.internal.telephony.flags.Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG)
+ @Test
+ fun satBasedIcon_reportsLevelZeroWhenOutOfService() =
+ testScope.runTest {
+ val latest by collectLastValue(underTest.signalLevelIcon)
+
+ // GIVEN a satellite connection
+ connectionRepository.isNonTerrestrial.value = true
+ // GIVEN this carrier has set INFLATE_SIGNAL_STRENGTH
+ connectionRepository.inflateSignalStrength.value = true
+
+ connectionRepository.primaryLevel.value = 4
+ assertThat(latest!!.level).isEqualTo(4)
+
+ connectionRepository.isInService.value = false
+ connectionRepository.primaryLevel.value = 4
+
+ // THEN level reports 0, by policy
+ assertThat(latest!!.level).isEqualTo(0)
+ }
+
private fun createInteractor(
overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
) =
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
index 64ae05131b5a..e6c98cd83b5e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
@@ -16,6 +16,8 @@
package com.android.systemui.keyguard.domain.interactor
+import android.service.dream.dreamManager
+import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.communal.domain.interactor.communalSceneInteractor
import com.android.systemui.communal.domain.interactor.communalSettingsInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
@@ -39,10 +41,12 @@ var Kosmos.fromDreamingTransitionInteractor by
mainDispatcher = testDispatcher,
keyguardInteractor = keyguardInteractor,
glanceableHubTransitions = glanceableHubTransitions,
+ communalInteractor = communalInteractor,
communalSceneInteractor = communalSceneInteractor,
communalSettingsInteractor = communalSettingsInteractor,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ dreamManager = dreamManager,
deviceEntryInteractor = deviceEntryInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
index 52416bae0d9d..ace11573c7c6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorKosmos.kt
@@ -41,6 +41,5 @@ val Kosmos.keyguardDismissInteractor by
trustRepository = trustRepository,
alternateBouncerInteractor = alternateBouncerInteractor,
powerInteractor = powerInteractor,
- keyguardTransitionInteractor = keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModelKosmos.kt
new file mode 100644
index 000000000000..ef10459b45cb
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DozingToGlanceableHubTransitionViewModelKosmos.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+@ExperimentalCoroutinesApi
+val Kosmos.dozingToGlanceableHubTransitionViewModel by Fixture {
+ DozingToGlanceableHubTransitionViewModel(animationFlow = keyguardTransitionAnimationFlow)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
index 2deeb253e925..cfc31c7f301c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt
@@ -20,6 +20,7 @@ import com.android.internal.logging.uiEventLogger
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.jank.interactionJankMonitor
import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.scene.domain.interactor.sceneBackInteractor
@@ -36,6 +37,7 @@ var Kosmos.statusBarStateController: SysuiStatusBarStateController by
uiEventLogger,
{ interactionJankMonitor },
mock(),
+ { keyguardInteractor },
{ keyguardTransitionInteractor },
{ shadeInteractor },
{ deviceUnlockedInteractor },
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStateCallbackInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStateCallbackInteractorKosmos.kt
new file mode 100644
index 000000000000..58dd522d40ec
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/domain/interactor/KeyguardStateCallbackInteractorKosmos.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.domain.interactor
+
+import com.android.keyguard.trustManager
+import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
+import com.android.systemui.keyguard.dismissCallbackRegistry
+import com.android.systemui.keyguard.domain.interactor.KeyguardStateCallbackInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.trustInteractor
+import com.android.systemui.keyguard.domain.interactor.windowManagerLockscreenVisibilityInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.user.domain.interactor.selectedUserInteractor
+
+val Kosmos.keyguardStateCallbackInteractor by
+ Kosmos.Fixture {
+ KeyguardStateCallbackInteractor(
+ applicationScope = testScope.backgroundScope,
+ backgroundDispatcher = testDispatcher,
+ selectedUserInteractor = selectedUserInteractor,
+ keyguardTransitionInteractor = keyguardTransitionInteractor,
+ trustInteractor = trustInteractor,
+ simBouncerInteractor = simBouncerInteractor,
+ dismissCallbackRegistry = dismissCallbackRegistry,
+ wmLockscreenVisibilityInteractor = windowManagerLockscreenVisibilityInteractor,
+ trustManager = trustManager,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
index ffd8aabdd964..a9e117affefb 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelKosmos.kt
@@ -25,6 +25,7 @@ import com.android.systemui.keyguard.ui.viewmodel.aodBurnInViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToGoneTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.aodToOccludedTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.dozingToGlanceableHubTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToLockscreenTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dozingToOccludedTransitionViewModel
import com.android.systemui.keyguard.ui.viewmodel.dreamingToLockscreenTransitionViewModel
@@ -66,6 +67,7 @@ val Kosmos.sharedNotificationContainerViewModel by Fixture {
aodToGoneTransitionViewModel = aodToGoneTransitionViewModel,
aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
aodToOccludedTransitionViewModel = aodToOccludedTransitionViewModel,
+ dozingToGlanceableHubTransitionViewModel = dozingToGlanceableHubTransitionViewModel,
dozingToLockscreenTransitionViewModel = dozingToLockscreenTransitionViewModel,
dozingToOccludedTransitionViewModel = dozingToOccludedTransitionViewModel,
dreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel,
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
index 1f98334bb8ce..c3b7087a44c3 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java
@@ -16,15 +16,7 @@
package com.android.server.appfunctions;
-import android.annotation.NonNull;
-import android.os.UserHandle;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
-
import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -41,50 +33,5 @@ public final class AppFunctionExecutors {
/* unit= */ TimeUnit.SECONDS,
/* workQueue= */ new LinkedBlockingQueue<>());
- /** A map of per-user executors for queued work. */
- @GuardedBy("sLock")
- private static final SparseArray<ExecutorService> mPerUserExecutorsLocked = new SparseArray<>();
-
- private static final Object sLock = new Object();
-
- /**
- * Returns a per-user executor for queued metadata sync request.
- *
- * <p>The work submitted to these executor (Sync request) needs to be synchronous per user hence
- * the use of a single thread.
- *
- * <p>Note: Use a different executor if not calling {@code submitSyncRequest} on a {@code
- * MetadataSyncAdapter}.
- */
- // TODO(b/357551503): Restrict the scope of this executor to the MetadataSyncAdapter itself.
- public static ExecutorService getPerUserSyncExecutor(@NonNull UserHandle user) {
- synchronized (sLock) {
- ExecutorService executor = mPerUserExecutorsLocked.get(user.getIdentifier(), null);
- if (executor == null) {
- executor = Executors.newSingleThreadExecutor();
- mPerUserExecutorsLocked.put(user.getIdentifier(), executor);
- }
- return executor;
- }
- }
-
- /**
- * Shuts down and removes the per-user executor for queued work.
- *
- * <p>This should be called when the user is removed.
- */
- public static void shutDownAndRemoveUserExecutor(@NonNull UserHandle user)
- throws InterruptedException {
- ExecutorService executor;
- synchronized (sLock) {
- executor = mPerUserExecutorsLocked.get(user.getIdentifier());
- mPerUserExecutorsLocked.remove(user.getIdentifier());
- }
- if (executor != null) {
- executor.shutdown();
- var unused = executor.awaitTermination(30, TimeUnit.SECONDS);
- }
- }
-
private AppFunctionExecutors() {}
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
index b4713d9f11af..1e723b5a1da2 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
@@ -95,12 +95,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
public void onUserStopping(@NonNull TargetUser user) {
Objects.requireNonNull(user);
- try {
- AppFunctionExecutors.shutDownAndRemoveUserExecutor(user.getUserHandle());
- MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
- } catch (InterruptedException e) {
- Slog.e(TAG, "Unable to remove data for: " + user.getUserHandle(), e);
- }
+ MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
}
@Override
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index e29b6e403f2a..d84b20556053 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -42,6 +42,7 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AndroidFuture;
import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults;
@@ -53,7 +54,9 @@ import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
/**
* This class implements helper methods for synchronously interacting with AppSearch while
@@ -63,9 +66,15 @@ import java.util.concurrent.Executor;
*/
public class MetadataSyncAdapter {
private static final String TAG = MetadataSyncAdapter.class.getSimpleName();
- private final Executor mSyncExecutor;
+
+ private final ExecutorService mExecutor;
+
private final AppSearchManager mAppSearchManager;
private final PackageManager mPackageManager;
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private Future<?> mCurrentSyncTask;
// Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility
// by permissions.
@@ -73,12 +82,10 @@ public class MetadataSyncAdapter {
public static final int EXECUTE_APP_FUNCTIONS_TRUSTED = 10;
public MetadataSyncAdapter(
- @NonNull Executor syncExecutor,
- @NonNull PackageManager packageManager,
- @NonNull AppSearchManager appSearchManager) {
- mSyncExecutor = Objects.requireNonNull(syncExecutor);
+ @NonNull PackageManager packageManager, @NonNull AppSearchManager appSearchManager) {
mPackageManager = Objects.requireNonNull(packageManager);
mAppSearchManager = Objects.requireNonNull(appSearchManager);
+ mExecutor = Executors.newSingleThreadExecutor();
}
/**
@@ -97,7 +104,7 @@ public class MetadataSyncAdapter {
AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB)
.build();
AndroidFuture<Boolean> settableSyncStatus = new AndroidFuture<>();
- mSyncExecutor.execute(
+ Runnable runnable =
() -> {
try (FutureAppSearchSession staticMetadataSearchSession =
new FutureAppSearchSessionImpl(
@@ -117,10 +124,23 @@ public class MetadataSyncAdapter {
} catch (Exception ex) {
settableSyncStatus.completeExceptionally(ex);
}
- });
+ };
+
+ synchronized (mLock) {
+ if (mCurrentSyncTask != null && !mCurrentSyncTask.isDone()) {
+ var unused = mCurrentSyncTask.cancel(false);
+ }
+ mCurrentSyncTask = mExecutor.submit(runnable);
+ }
+
return settableSyncStatus;
}
+ /** This method shuts down the {@link MetadataSyncAdapter} scheduler. */
+ public void shutDown() {
+ mExecutor.shutdown();
+ }
+
@WorkerThread
@VisibleForTesting
void trySyncAppFunctionMetadataBlocking(
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
index f421527e72d0..e933ec1ba4b1 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java
@@ -55,10 +55,7 @@ public final class MetadataSyncPerUser {
PackageManager perUserPackageManager = userContext.getPackageManager();
if (perUserAppSearchManager != null) {
metadataSyncAdapter =
- new MetadataSyncAdapter(
- AppFunctionExecutors.getPerUserSyncExecutor(user),
- perUserPackageManager,
- perUserAppSearchManager);
+ new MetadataSyncAdapter(perUserPackageManager, perUserAppSearchManager);
sPerUserMetadataSyncAdapter.put(user.getIdentifier(), metadataSyncAdapter);
return metadataSyncAdapter;
}
@@ -74,7 +71,12 @@ public final class MetadataSyncPerUser {
*/
public static void removeUserSyncAdapter(UserHandle user) {
synchronized (sLock) {
- sPerUserMetadataSyncAdapter.remove(user.getIdentifier());
+ MetadataSyncAdapter metadataSyncAdapter =
+ sPerUserMetadataSyncAdapter.get(user.getIdentifier(), null);
+ if (metadataSyncAdapter != null) {
+ metadataSyncAdapter.shutDown();
+ sPerUserMetadataSyncAdapter.remove(user.getIdentifier());
+ }
}
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 6ae6f3d4713a..6af4be50b00c 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -619,7 +619,7 @@ public class AppOpsService extends IAppOpsService.Stub {
this.op = op;
this.uid = uid;
this.uidState = uidState;
- this.packageName = packageName;
+ this.packageName = packageName.intern();
// We keep an invariant that the persistent device will always have an entry in
// mDeviceAttributedOps.
mDeviceAttributedOps.put(PERSISTENT_DEVICE_ID_DEFAULT,
@@ -1031,7 +1031,7 @@ public class AppOpsService extends IAppOpsService.Stub {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- String pkgName = intent.getData().getEncodedSchemeSpecificPart();
+ String pkgName = intent.getData().getEncodedSchemeSpecificPart().intern();
int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
if (action.equals(ACTION_PACKAGE_ADDED)
@@ -1235,7 +1235,7 @@ public class AppOpsService extends IAppOpsService.Stub {
Ops ops = uidState.pkgOps.get(packageName);
if (ops == null) {
ops = new Ops(packageName, uidState);
- uidState.pkgOps.put(packageName, ops);
+ uidState.pkgOps.put(packageName.intern(), ops);
}
SparseIntArray packageModes =
@@ -4739,7 +4739,7 @@ public class AppOpsService extends IAppOpsService.Stub {
return null;
}
ops = new Ops(packageName, uidState);
- uidState.pkgOps.put(packageName, ops);
+ uidState.pkgOps.put(packageName.intern(), ops);
}
if (edit) {
@@ -5076,7 +5076,7 @@ public class AppOpsService extends IAppOpsService.Stub {
Ops ops = uidState.pkgOps.get(pkgName);
if (ops == null) {
ops = new Ops(pkgName, uidState);
- uidState.pkgOps.put(pkgName, ops);
+ uidState.pkgOps.put(pkgName.intern(), ops);
}
ops.put(op.op, op);
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 55d9c6eac87a..13e3348b6a04 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -17,6 +17,11 @@ package com.android.server.audio;
import static android.media.audio.Flags.scoManagedByAudio;
+import static com.android.media.audio.Flags.equalScoLeaVcIndexRange;
+import static com.android.server.audio.AudioService.BT_COMM_DEVICE_ACTIVE_BLE_HEADSET;
+import static com.android.server.audio.AudioService.BT_COMM_DEVICE_ACTIVE_BLE_SPEAKER;
+import static com.android.server.audio.AudioService.BT_COMM_DEVICE_ACTIVE_SCO;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.compat.CompatChanges;
@@ -64,6 +69,7 @@ import android.util.Pair;
import android.util.PrintWriterPrinter;
import com.android.internal.annotations.GuardedBy;
+import com.android.server.audio.AudioService.BtCommDeviceActiveType;
import com.android.server.utils.EventLogger;
import java.io.PrintWriter;
@@ -835,15 +841,15 @@ public class AudioDeviceBroker {
return isDeviceOnForCommunication(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
}
- /*package*/ boolean isBluetoothScoActive() {
+ private boolean isBluetoothScoActive() {
return isDeviceActiveForCommunication(AudioDeviceInfo.TYPE_BLUETOOTH_SCO);
}
- /*package*/ boolean isBluetoothBleHeadsetActive() {
+ private boolean isBluetoothBleHeadsetActive() {
return isDeviceActiveForCommunication(AudioDeviceInfo.TYPE_BLE_HEADSET);
}
- /*package*/ boolean isBluetoothBleSpeakerActive() {
+ private boolean isBluetoothBleSpeakerActive() {
return isDeviceActiveForCommunication(AudioDeviceInfo.TYPE_BLE_SPEAKER);
}
@@ -1437,7 +1443,20 @@ public class AudioDeviceBroker {
}
mCurCommunicationPortId = portId;
- mAudioService.postScoDeviceActive(isBluetoothScoActive());
+ @BtCommDeviceActiveType int btCommDeviceActiveType = 0;
+ if (equalScoLeaVcIndexRange()) {
+ if (isBluetoothScoActive()) {
+ btCommDeviceActiveType = BT_COMM_DEVICE_ACTIVE_SCO;
+ } else if (isBluetoothBleHeadsetActive()) {
+ btCommDeviceActiveType = BT_COMM_DEVICE_ACTIVE_BLE_HEADSET;
+ } else if (isBluetoothBleSpeakerActive()) {
+ btCommDeviceActiveType = BT_COMM_DEVICE_ACTIVE_BLE_SPEAKER;
+ }
+ mAudioService.postBtCommDeviceActive(btCommDeviceActiveType);
+ } else {
+ mAudioService.postBtCommDeviceActive(
+ isBluetoothScoActive() ? BT_COMM_DEVICE_ACTIVE_SCO : btCommDeviceActiveType);
+ }
final int nbDispatchers = mCommDevDispatchers.beginBroadcast();
for (int i = 0; i < nbDispatchers; i++) {
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index df69afe6ed79..e83b03690a24 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -66,6 +66,7 @@ import static com.android.media.audio.Flags.alarmMinVolumeZero;
import static com.android.media.audio.Flags.asDeviceConnectionFailure;
import static com.android.media.audio.Flags.audioserverPermissions;
import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
+import static com.android.media.audio.Flags.equalScoLeaVcIndexRange;
import static com.android.media.audio.Flags.replaceStreamBtSco;
import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
import static com.android.media.audio.Flags.setStreamVolumeOrder;
@@ -470,7 +471,7 @@ public class AudioService extends IAudioService.Stub
private static final int MSG_CONFIGURATION_CHANGED = 54;
private static final int MSG_BROADCAST_MASTER_MUTE = 55;
private static final int MSG_UPDATE_CONTEXTUAL_VOLUMES = 56;
- private static final int MSG_SCO_DEVICE_ACTIVE_UPDATE = 57;
+ private static final int MSG_BT_COMM_DEVICE_ACTIVE_UPDATE = 57;
/**
* Messages handled by the {@link SoundDoseHelper}, do not exceed
@@ -766,7 +767,21 @@ public class AudioService extends IAudioService.Stub
* @see System#MUTE_STREAMS_AFFECTED */
private int mUserMutableStreams;
- private final AtomicBoolean mScoDeviceActive = new AtomicBoolean(false);
+ /** The active bluetooth device type used for communication is sco. */
+ /*package*/ static final int BT_COMM_DEVICE_ACTIVE_SCO = 1;
+ /** The active bluetooth device type used for communication is ble headset. */
+ /*package*/ static final int BT_COMM_DEVICE_ACTIVE_BLE_HEADSET = 1 << 1;
+ /** The active bluetooth device type used for communication is ble speaker. */
+ /*package*/ static final int BT_COMM_DEVICE_ACTIVE_BLE_SPEAKER = 1 << 2;
+ @IntDef({
+ BT_COMM_DEVICE_ACTIVE_SCO, BT_COMM_DEVICE_ACTIVE_BLE_HEADSET,
+ BT_COMM_DEVICE_ACTIVE_BLE_SPEAKER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BtCommDeviceActiveType {
+ }
+
+ private final AtomicInteger mBtCommDeviceActive = new AtomicInteger(0);
@NonNull
private SoundEffectsHelper mSfxHelper;
@@ -2522,12 +2537,18 @@ public class AudioService extends IAudioService.Stub
// this should not happen, throwing exception
throw new IllegalArgumentException("STREAM_BLUETOOTH_SCO is deprecated");
}
- return streamType == AudioSystem.STREAM_VOICE_CALL && mScoDeviceActive.get();
+ return streamType == AudioSystem.STREAM_VOICE_CALL
+ && mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO;
} else {
return streamType == AudioSystem.STREAM_BLUETOOTH_SCO;
}
}
+ private boolean isStreamBluetoothComm(int streamType) {
+ return (streamType == AudioSystem.STREAM_VOICE_CALL && mBtCommDeviceActive.get() != 0)
+ || streamType == AudioSystem.STREAM_BLUETOOTH_SCO;
+ }
+
private void dumpStreamStates(PrintWriter pw) {
pw.println("\nStream volumes (device: index)");
int numStreamTypes = AudioSystem.getNumStreamTypes();
@@ -4761,7 +4782,7 @@ public class AudioService extends IAudioService.Stub
+ asDeviceConnectionFailure());
pw.println("\tandroid.media.audio.autoPublicVolumeApiHardening:"
+ autoPublicVolumeApiHardening());
- pw.println("\tandroid.media.audio.Flags.automaticBtDeviceType:"
+ pw.println("\tandroid.media.audio.automaticBtDeviceType:"
+ automaticBtDeviceType());
pw.println("\tandroid.media.audio.featureSpatialAudioHeadtrackingLowLatency:"
+ featureSpatialAudioHeadtrackingLowLatency());
@@ -4783,6 +4804,8 @@ public class AudioService extends IAudioService.Stub
+ absVolumeIndexFix());
pw.println("\tcom.android.media.audio.replaceStreamBtSco:"
+ replaceStreamBtSco());
+ pw.println("\tcom.android.media.audio.equalScoLeaVcIndexRange:"
+ + equalScoLeaVcIndexRange());
}
private void dumpAudioMode(PrintWriter pw) {
@@ -4896,7 +4919,7 @@ public class AudioService extends IAudioService.Stub
final VolumeStreamState streamState = getVssForStreamOrDefault(streamTypeAlias);
if (!replaceStreamBtSco() && (streamType == AudioManager.STREAM_VOICE_CALL)
- && isInCommunication() && mDeviceBroker.isBluetoothScoActive()) {
+ && isInCommunication() && mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) {
Log.i(TAG, "setStreamVolume for STREAM_VOICE_CALL, switching to STREAM_BLUETOOTH_SCO");
streamType = AudioManager.STREAM_BLUETOOTH_SCO;
}
@@ -5947,10 +5970,10 @@ public class AudioService extends IAudioService.Stub
final boolean ringerModeMute = ringerMode == AudioManager.RINGER_MODE_VIBRATE
|| ringerMode == AudioManager.RINGER_MODE_SILENT;
final boolean shouldRingSco = ringerMode == AudioManager.RINGER_MODE_VIBRATE
- && mDeviceBroker.isBluetoothScoActive();
+ && mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO;
final boolean shouldRingBle = ringerMode == AudioManager.RINGER_MODE_VIBRATE
- && (mDeviceBroker.isBluetoothBleHeadsetActive()
- || mDeviceBroker.isBluetoothBleSpeakerActive());
+ && (mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_BLE_HEADSET
+ || mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_BLE_SPEAKER);
// Ask audio policy engine to force use Bluetooth SCO/BLE channel if needed
final String eventSource = "muteRingerModeStreams() from u/pid:" + Binder.getCallingUid()
+ "/" + Binder.getCallingPid();
@@ -7419,7 +7442,8 @@ public class AudioService extends IAudioService.Stub
case AudioSystem.PLATFORM_VOICE:
if (isInCommunication()
|| mAudioSystem.isStreamActive(AudioManager.STREAM_VOICE_CALL, 0)) {
- if (!replaceStreamBtSco() && mDeviceBroker.isBluetoothScoActive()) {
+ if (!replaceStreamBtSco()
+ && mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) {
if (DEBUG_VOL) {
Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO...");
}
@@ -7463,7 +7487,8 @@ public class AudioService extends IAudioService.Stub
}
default:
if (isInCommunication()) {
- if (!replaceStreamBtSco() && mDeviceBroker.isBluetoothScoActive()) {
+ if (!replaceStreamBtSco()
+ && mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) {
if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_BLUETOOTH_SCO");
return AudioSystem.STREAM_BLUETOOTH_SCO;
} else {
@@ -7788,15 +7813,15 @@ public class AudioService extends IAudioService.Stub
0 /*delay*/);
}
- /*package*/ void postScoDeviceActive(boolean scoDeviceActive) {
+ /*package*/ void postBtCommDeviceActive(@BtCommDeviceActiveType int btCommDeviceActive) {
sendMsg(mAudioHandler,
- MSG_SCO_DEVICE_ACTIVE_UPDATE,
- SENDMSG_QUEUE, scoDeviceActive ? 1 : 0 /*arg1*/, 0 /*arg2*/, null /*obj*/,
+ MSG_BT_COMM_DEVICE_ACTIVE_UPDATE,
+ SENDMSG_QUEUE, btCommDeviceActive /*arg1*/, 0 /*arg2*/, null /*obj*/,
0 /*delay*/);
}
- private void onUpdateScoDeviceActive(boolean scoDeviceActive) {
- if (mScoDeviceActive.compareAndSet(!scoDeviceActive, scoDeviceActive)) {
+ private void onUpdateBtCommDeviceActive(@BtCommDeviceActiveType int btCommDeviceActive) {
+ if (mBtCommDeviceActive.getAndSet(btCommDeviceActive) != btCommDeviceActive) {
getVssForStreamOrDefault(AudioSystem.STREAM_VOICE_CALL).updateIndexFactors();
}
}
@@ -8997,7 +9022,7 @@ public class AudioService extends IAudioService.Stub
}
public void updateIndexFactors() {
- if (!replaceStreamBtSco()) {
+ if (!replaceStreamBtSco() && !equalScoLeaVcIndexRange()) {
return;
}
@@ -9008,10 +9033,18 @@ public class AudioService extends IAudioService.Stub
mIndexMax = MAX_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10;
}
- // SCO devices have a different min index
- if (isStreamBluetoothSco(mStreamType)) {
+ if (!equalScoLeaVcIndexRange() && isStreamBluetoothSco(mStreamType)) {
+ // SCO devices have a different min index
mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10;
mIndexStepFactor = 1.f;
+ } else if (equalScoLeaVcIndexRange() && isStreamBluetoothComm(mStreamType)) {
+ // For non SCO devices the stream state does not change the min index
+ if (mBtCommDeviceActive.get() == BT_COMM_DEVICE_ACTIVE_SCO) {
+ mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_BLUETOOTH_SCO] * 10;
+ } else {
+ mIndexMin = MIN_STREAM_VOLUME[mStreamType] * 10;
+ }
+ mIndexStepFactor = 1.f;
} else {
mIndexMin = MIN_STREAM_VOLUME[AudioSystem.STREAM_VOICE_CALL] * 10;
mIndexStepFactor = (float) (mIndexMax - mIndexMin) / (float) (
@@ -9207,7 +9240,7 @@ public class AudioService extends IAudioService.Stub
private void setStreamVolumeIndex(int index, int device) {
// Only set audio policy BT SCO stream volume to 0 when the stream is actually muted.
// This allows RX path muting by the audio HAL only when explicitly muted but not when
- // index is just set to 0 to repect BT requirements
+ // index is just set to 0 to respect BT requirements
if (isStreamBluetoothSco(mStreamType) && index == 0 && !isFullyMuted()) {
index = 1;
}
@@ -10217,8 +10250,8 @@ public class AudioService extends IAudioService.Stub
onUpdateContextualVolumes();
break;
- case MSG_SCO_DEVICE_ACTIVE_UPDATE:
- onUpdateScoDeviceActive(msg.arg1 != 0);
+ case MSG_BT_COMM_DEVICE_ACTIVE_UPDATE:
+ onUpdateBtCommDeviceActive(msg.arg1);
break;
case MusicFxHelper.MSG_EFFECT_CLIENT_GONE:
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 5d850896d5de..2d802b21cf03 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -49,7 +49,6 @@ import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.PromptInfo;
import android.hardware.biometrics.SensorLocationInternal;
import android.hardware.biometrics.SensorPropertiesInternal;
-import android.hardware.biometrics.face.IFace;
import android.hardware.face.FaceSensorConfigurations;
import android.hardware.face.FaceSensorProperties;
import android.hardware.face.FaceSensorPropertiesInternal;
@@ -73,6 +72,7 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.server.SystemService;
+import com.android.server.biometrics.sensors.face.FaceService;
import com.android.server.biometrics.sensors.fingerprint.FingerprintService;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
@@ -211,7 +211,7 @@ public class AuthService extends SystemService {
*/
@VisibleForTesting
public String[] getFaceAidlInstances() {
- return ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
+ return FaceService.getDeclaredInstances();
}
/**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index bd6d59391e4a..8c988729a1ae 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -19,6 +19,7 @@ package com.android.server.biometrics.sensors.face;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.MANAGE_FACE;
import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
+import static android.hardware.face.FaceSensorConfigurations.getIFace;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -60,6 +61,7 @@ import android.util.proto.ProtoOutputStream;
import android.view.Surface;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.SystemService;
@@ -753,7 +755,7 @@ public class FaceService extends SystemService {
public FaceService(Context context) {
this(context, null /* faceProviderFunction */, () -> IBiometricService.Stub.asInterface(
ServiceManager.getService(Context.BIOMETRIC_SERVICE)), null /* faceProvider */,
- () -> ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR));
+ () -> getDeclaredInstances());
}
@VisibleForTesting FaceService(Context context,
@@ -778,8 +780,7 @@ public class FaceService extends SystemService {
mFaceProvider = faceProvider != null ? faceProvider : (name) -> {
final String fqName = IFace.DESCRIPTOR + "/" + name;
- final IFace face = IFace.Stub.asInterface(
- Binder.allowBlocking(ServiceManager.waitForDeclaredService(fqName)));
+ final IFace face = getIFace(fqName);
if (face == null) {
Slog.e(TAG, "Unable to get declared service: " + fqName);
return null;
@@ -835,6 +836,23 @@ public class FaceService extends SystemService {
*/
public static native void releaseSurfaceHandle(@NonNull NativeHandle handle);
+ /**
+ * Get all face hal instances declared in manifest
+ * @return instance names
+ */
+ public static String[] getDeclaredInstances() {
+ String[] a = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
+ Slog.i(TAG, "Before:getDeclaredInstances: IFace instance found, a.length="
+ + a.length);
+ if (!ArrayUtils.contains(a, "virtual")) {
+ // Now, the virtual hal is registered with IVirtualHal interface and it is also
+ // moved from vendor to system_ext partition without a device manifest. So
+ // if the old vhal is not declared, add here.
+ a = ArrayUtils.appendElement(String.class, a, "virtual");
+ }
+ Slog.i(TAG, "After:getDeclaredInstances: a.length=" + a.length);
+ return a;
+ }
void syncEnrollmentsNow() {
Utils.checkPermissionOrShell(getContext(), MANAGE_FACE);
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
index dca14914a572..3ed01d5a2cc9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/BiometricTestSessionImpl.java
@@ -23,6 +23,9 @@ import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.face.AuthenticationFrame;
import android.hardware.biometrics.face.BaseFrame;
import android.hardware.biometrics.face.EnrollmentFrame;
+import android.hardware.biometrics.face.virtualhal.AcquiredInfoAndVendorCode;
+import android.hardware.biometrics.face.virtualhal.EnrollmentProgressStep;
+import android.hardware.biometrics.face.virtualhal.NextEnrollment;
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticationFrame;
import android.hardware.face.FaceEnrollFrame;
@@ -50,6 +53,7 @@ import java.util.Set;
public class BiometricTestSessionImpl extends ITestSession.Stub {
private static final String TAG = "face/aidl/BiometricTestSessionImpl";
+ private static final int VHAL_ENROLLMENT_ID = 9999;
@NonNull private final Context mContext;
private final int mSensorId;
@@ -144,16 +148,35 @@ public class BiometricTestSessionImpl extends ITestSession.Stub {
super.setTestHalEnabled_enforcePermission();
- mProvider.setTestHalEnabled(enabled);
mSensor.setTestHalEnabled(enabled);
+ mProvider.setTestHalEnabled(enabled);
}
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
@Override
- public void startEnroll(int userId) {
+ public void startEnroll(int userId) throws RemoteException {
super.startEnroll_enforcePermission();
+ Slog.i(TAG, "startEnroll(): isVhalForTesting=" + mProvider.isVhalForTesting());
+ if (mProvider.isVhalForTesting()) {
+ final AcquiredInfoAndVendorCode[] acquiredInfoAndVendorCodes =
+ {new AcquiredInfoAndVendorCode()};
+ final EnrollmentProgressStep[] enrollmentProgressSteps =
+ {new EnrollmentProgressStep(), new EnrollmentProgressStep()};
+ enrollmentProgressSteps[0].durationMs = 100;
+ enrollmentProgressSteps[0].acquiredInfoAndVendorCodes = acquiredInfoAndVendorCodes;
+ enrollmentProgressSteps[1].durationMs = 200;
+ enrollmentProgressSteps[1].acquiredInfoAndVendorCodes = acquiredInfoAndVendorCodes;
+
+ final NextEnrollment nextEnrollment = new NextEnrollment();
+ nextEnrollment.id = VHAL_ENROLLMENT_ID;
+ nextEnrollment.progressSteps = enrollmentProgressSteps;
+ nextEnrollment.result = true;
+ mProvider.getVhal().setNextEnrollment(nextEnrollment);
+ mProvider.getVhal().setOperationAuthenticateDuration(6000);
+ }
+
mProvider.scheduleEnroll(mSensorId, new Binder(), new byte[69], userId, mReceiver,
mContext.getOpPackageName(), new int[0] /* disabledFeatures */,
null /* previewSurface */, false /* debugConsent */,
@@ -166,6 +189,10 @@ public class BiometricTestSessionImpl extends ITestSession.Stub {
super.finishEnroll_enforcePermission();
+ if (mProvider.isVhalForTesting()) {
+ return;
+ }
+
int nextRandomId = mRandom.nextInt();
while (mEnrollmentIds.contains(nextRandomId)) {
nextRandomId = mRandom.nextInt();
@@ -178,11 +205,16 @@ public class BiometricTestSessionImpl extends ITestSession.Stub {
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
@Override
- public void acceptAuthentication(int userId) {
+ public void acceptAuthentication(int userId) throws RemoteException {
// Fake authentication with any of the existing faces
super.acceptAuthentication_enforcePermission();
+ if (mProvider.isVhalForTesting()) {
+ mProvider.getVhal().setEnrollmentHit(VHAL_ENROLLMENT_ID);
+ return;
+ }
+
List<Face> faces = FaceUtils.getInstance(mSensorId)
.getBiometricsForUser(mContext, userId);
if (faces.isEmpty()) {
@@ -196,10 +228,15 @@ public class BiometricTestSessionImpl extends ITestSession.Stub {
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
@Override
- public void rejectAuthentication(int userId) {
+ public void rejectAuthentication(int userId) throws RemoteException {
super.rejectAuthentication_enforcePermission();
+ if (mProvider.isVhalForTesting()) {
+ mProvider.getVhal().setEnrollmentHit(VHAL_ENROLLMENT_ID + 1);
+ return;
+ }
+
mSensor.getSessionForUser(userId).getHalSessionCallback().onAuthenticationFailed();
}
@@ -236,11 +273,17 @@ public class BiometricTestSessionImpl extends ITestSession.Stub {
@android.annotation.EnforcePermission(android.Manifest.permission.TEST_BIOMETRIC)
@Override
- public void cleanupInternalState(int userId) {
+ public void cleanupInternalState(int userId) throws RemoteException {
super.cleanupInternalState_enforcePermission();
Slog.d(TAG, "cleanupInternalState: " + userId);
+
+ if (mProvider.isVhalForTesting()) {
+ Slog.i(TAG, "cleanup virtualhal configurations");
+ mProvider.getVhal().resetConfigurations(); //setEnrollments(new int[]{});
+ }
+
mProvider.scheduleInternalCleanup(mSensorId, userId, new ClientMonitorCallback() {
@Override
public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
index bb213bfa79e6..5127e68a9df3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java
@@ -16,6 +16,9 @@
package com.android.server.biometrics.sensors.face.aidl;
+import static android.hardware.face.FaceSensorConfigurations.getIFace;
+import static android.hardware.face.FaceSensorConfigurations.remapFqName;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
@@ -32,6 +35,7 @@ import android.hardware.biometrics.ITestSession;
import android.hardware.biometrics.ITestSessionCallback;
import android.hardware.biometrics.face.IFace;
import android.hardware.biometrics.face.SensorProps;
+import android.hardware.biometrics.face.virtualhal.IVirtualHal;
import android.hardware.face.Face;
import android.hardware.face.FaceAuthenticateOptions;
import android.hardware.face.FaceEnrollOptions;
@@ -54,6 +58,7 @@ import com.android.server.biometrics.AuthenticationStatsBroadcastReceiver;
import com.android.server.biometrics.AuthenticationStatsCollector;
import com.android.server.biometrics.BiometricDanglingReceiver;
import com.android.server.biometrics.BiometricHandlerProvider;
+import com.android.server.biometrics.Flags;
import com.android.server.biometrics.Utils;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
@@ -130,6 +135,11 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
private AuthenticationStatsCollector mAuthenticationStatsCollector;
@Nullable
private IFace mDaemon;
+ @Nullable
+ private IVirtualHal mVhal;
+ @Nullable
+ private String mHalInstanceNameCurrent;
+
private final class BiometricTaskStackListener extends TaskStackListener {
@Override
@@ -286,14 +296,37 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
if (mTestHalEnabled) {
return true;
}
- return ServiceManager.checkService(IFace.DESCRIPTOR + "/" + mHalInstanceName) != null;
+ return ServiceManager.checkService(
+ remapFqName(IFace.DESCRIPTOR + "/" + mHalInstanceName)) != null;
}
@Nullable
@VisibleForTesting
synchronized IFace getHalInstance() {
if (mTestHalEnabled) {
- return new TestHal();
+ if (Flags.useVhalForTesting()) {
+ if (!mHalInstanceNameCurrent.contains("virtual")) {
+ Slog.i(getTag(), "Switching face hal from " + mHalInstanceName
+ + " to virtual hal");
+ mHalInstanceNameCurrent = "virtual";
+ mDaemon = null;
+ }
+ } else {
+ // Enabling the test HAL for a single sensor in a multi-sensor HAL currently enables
+ // the test HAL for all sensors under that HAL. This can be updated in the future if
+ // necessary.
+ return new TestHal();
+ }
+ } else {
+ if (mHalInstanceNameCurrent == null) {
+ mHalInstanceNameCurrent = mHalInstanceName;
+ } else if (mHalInstanceNameCurrent.contains("virtual")
+ && mHalInstanceNameCurrent != mHalInstanceName) {
+ Slog.i(getTag(), "Switching face from virtual hal " + "to "
+ + mHalInstanceName);
+ mHalInstanceNameCurrent = mHalInstanceName;
+ mDaemon = null;
+ }
}
if (mDaemon != null) {
@@ -302,10 +335,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
Slog.d(getTag(), "Daemon was null, reconnecting");
- mDaemon = IFace.Stub.asInterface(
- Binder.allowBlocking(
- ServiceManager.waitForDeclaredService(
- IFace.DESCRIPTOR + "/" + mHalInstanceName)));
+ mDaemon = getIFace(IFace.DESCRIPTOR + "/" + mHalInstanceNameCurrent);
if (mDaemon == null) {
Slog.e(getTag(), "Unable to get daemon");
return null;
@@ -833,7 +863,13 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
}
void setTestHalEnabled(boolean enabled) {
+ final boolean changed = enabled != mTestHalEnabled;
mTestHalEnabled = enabled;
+ Slog.i(getTag(), "setTestHalEnabled(): isVhalForTestingFlags=" + Flags.useVhalForTesting()
+ + " mTestHalEnabled=" + mTestHalEnabled + " changed=" + changed);
+ if (changed && isVhalForTesting()) {
+ getHalInstance();
+ }
}
@Override
@@ -851,9 +887,40 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider {
}
/**
+ * Return true if vhal_for_testing feature is enabled and test is active
+ */
+ public boolean isVhalForTesting() {
+ return (Flags.useVhalForTesting() && mTestHalEnabled);
+ }
+
+
+ /**
* Sends a face re enroll notification.
*/
public void sendFaceReEnrollNotification() {
mAuthenticationStatsCollector.sendFaceReEnrollNotification();
}
+
+ /**
+ * Sends a fingerprint enroll notification.
+ */
+ public void sendFingerprintReEnrollNotification() {
+ mAuthenticationStatsCollector.sendFingerprintReEnrollNotification();
+ }
+
+ /**
+ * Return virtual hal AIDL interface if it is used for testing
+ *
+ */
+ public IVirtualHal getVhal() throws RemoteException {
+ if (mVhal == null && isVhalForTesting()) {
+ mVhal = IVirtualHal.Stub.asInterface(
+ Binder.allowBlocking(
+ ServiceManager.waitForService(
+ IVirtualHal.DESCRIPTOR + "/"
+ + mHalInstanceNameCurrent)));
+ Slog.d(getTag(), "getVhal " + mHalInstanceNameCurrent);
+ }
+ return mVhal;
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 6f9534993a3f..9fddcfc199b9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -17,6 +17,7 @@
package com.android.server.biometrics.sensors.face.aidl;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_HW_UNAVAILABLE;
+import static android.hardware.face.FaceSensorConfigurations.remapFqName;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -337,7 +338,8 @@ public class Sensor {
if (mTestHalEnabled) {
return true;
}
- return ServiceManager.checkService(IFace.DESCRIPTOR + "/" + halInstanceName) != null;
+ return ServiceManager.checkService(
+ remapFqName(IFace.DESCRIPTOR + "/" + halInstanceName)) != null;
}
/**
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e7fd8f7db182..ae33b83b49dc 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -571,6 +571,10 @@ public final class DisplayManagerService extends SystemService {
private final DisplayNotificationManager mDisplayNotificationManager;
private final ExternalDisplayStatsService mExternalDisplayStatsService;
+ // Manages the relative placement of extended displays
+ @Nullable
+ private final DisplayTopologyCoordinator mDisplayTopologyCoordinator;
+
/**
* Applications use {@link android.view.Display#getRefreshRate} and
* {@link android.view.Display.Mode#getRefreshRate} to know what is the display refresh rate.
@@ -644,6 +648,11 @@ public final class DisplayManagerService extends SystemService {
mDisplayNotificationManager = new DisplayNotificationManager(mFlags, mContext,
mExternalDisplayStatsService);
mExternalDisplayPolicy = new ExternalDisplayPolicy(new ExternalDisplayPolicyInjector());
+ if (mFlags.isDisplayTopologyEnabled()) {
+ mDisplayTopologyCoordinator = new DisplayTopologyCoordinator();
+ } else {
+ mDisplayTopologyCoordinator = null;
+ }
}
public void setupSchedulerPolicies() {
@@ -3474,9 +3483,13 @@ public final class DisplayManagerService extends SystemService {
mSmallAreaDetectionController.dump(pw);
}
+ if (mDisplayTopologyCoordinator != null) {
+ pw.println();
+ mDisplayTopologyCoordinator.dump(pw);
+ }
+
pw.println();
mFlags.dump(pw);
-
}
private static float[] getFloatArray(TypedArray array) {
diff --git a/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
new file mode 100644
index 000000000000..631f14755b12
--- /dev/null
+++ b/services/core/java/com/android/server/display/DisplayTopologyCoordinator.java
@@ -0,0 +1,104 @@
+/*
+ * 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.display;
+
+import android.annotation.Nullable;
+import android.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class manages the relative placement (topology) of extended displays. It is responsible for
+ * updating and persisting the topology.
+ */
+class DisplayTopologyCoordinator {
+
+ /**
+ * The topology tree
+ */
+ @Nullable
+ private TopologyTreeNode mRoot;
+
+ /**
+ * The logical display ID of the primary display that will show certain UI elements.
+ * This is not necessarily the same as the default display.
+ */
+ private int mPrimaryDisplayId;
+
+ /**
+ * Print the object's state and debug information into the given stream.
+ * @param pw The stream to dump information to.
+ */
+ public void dump(PrintWriter pw) {
+ pw.println("DisplayTopologyCoordinator:");
+ pw.println("--------------------");
+ IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
+ ipw.increaseIndent();
+
+ ipw.println("mPrimaryDisplayId: " + mPrimaryDisplayId);
+
+ ipw.println("Topology tree:");
+ if (mRoot != null) {
+ ipw.increaseIndent();
+ mRoot.dump(ipw);
+ ipw.decreaseIndent();
+ }
+ }
+
+ private static class TopologyTreeNode {
+
+ /**
+ * The logical display ID
+ */
+ private int mDisplayId;
+
+ private final List<TopologyTreeNode> mChildren = new ArrayList<>();
+
+ /**
+ * The position of this display relative to its parent.
+ */
+ private Position mPosition;
+
+ /**
+ * The distance from the top edge of the parent display to the top edge of this display (in
+ * case of POSITION_LEFT or POSITION_RIGHT) or from the left edge of the parent display
+ * to the left edge of this display (in case of POSITION_TOP or POSITION_BOTTOM). The unit
+ * used is density-independent pixels (dp).
+ */
+ private double mOffset;
+
+ /**
+ * Print the object's state and debug information into the given stream.
+ * @param ipw The stream to dump information to.
+ */
+ void dump(IndentingPrintWriter ipw) {
+ ipw.println("Display {id=" + mDisplayId + ", position=" + mPosition
+ + ", offset=" + mOffset + "}");
+ ipw.increaseIndent();
+ for (TopologyTreeNode child : mChildren) {
+ child.dump(ipw);
+ }
+ ipw.decreaseIndent();
+ }
+
+ private enum Position {
+ POSITION_LEFT, POSITION_TOP, POSITION_RIGHT, POSITION_BOTTOM
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index f600e7fc2946..df66893a2f35 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -69,6 +69,10 @@ public class DisplayManagerFlags {
Flags.FLAG_ENABLE_MODE_LIMIT_FOR_EXTERNAL_DISPLAY,
Flags::enableModeLimitForExternalDisplay);
+ private final FlagState mDisplayTopology = new FlagState(
+ Flags.FLAG_DISPLAY_TOPOLOGY,
+ Flags::displayTopology);
+
private final FlagState mConnectedDisplayErrorHandlingFlagState = new FlagState(
Flags.FLAG_ENABLE_CONNECTED_DISPLAY_ERROR_HANDLING,
Flags::enableConnectedDisplayErrorHandling);
@@ -266,6 +270,10 @@ public class DisplayManagerFlags {
return mExternalDisplayLimitModeState.isEnabled();
}
+ public boolean isDisplayTopologyEnabled() {
+ return mDisplayTopology.isEnabled();
+ }
+
/**
* @return Whether displays refresh rate synchronization is enabled.
*/
@@ -441,6 +449,7 @@ public class DisplayManagerFlags {
pw.println(" " + mConnectedDisplayManagementFlagState);
pw.println(" " + mDisplayOffloadFlagState);
pw.println(" " + mExternalDisplayLimitModeState);
+ pw.println(" " + mDisplayTopology);
pw.println(" " + mHdrClamperFlagState);
pw.println(" " + mNbmControllerFlagState);
pw.println(" " + mPowerThrottlingClamperFlagState);
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 9968ba57bba4..e3ebe5bcd9ed 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -92,6 +92,14 @@ flag {
}
flag {
+ name: "display_topology"
+ namespace: "display_manager"
+ description: "Display topology for moving cursors and windows between extended displays"
+ bug: "278199220"
+ is_fixed_read_only: true
+}
+
+flag {
name: "enable_displays_refresh_rates_synchronization"
namespace: "display_manager"
description: "Enables synchronization of refresh rates across displays"
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
index f3514653518b..a1e5ebc002a5 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugView.java
@@ -24,7 +24,6 @@ import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.graphics.Rect;
-import android.hardware.input.InputManager;
import android.util.Slog;
import android.util.TypedValue;
import android.view.Gravity;
@@ -42,6 +41,7 @@ import com.android.server.input.TouchpadHardwareProperties;
import com.android.server.input.TouchpadHardwareState;
import java.util.Objects;
+import java.util.function.Consumer;
public class TouchpadDebugView extends LinearLayout {
private static final float MAX_SCREEN_WIDTH_PROPORTION = 0.4f;
@@ -54,7 +54,6 @@ public class TouchpadDebugView extends LinearLayout {
private static final int ROUNDED_CORNER_RADIUS_DP = 24;
private static final int BUTTON_PRESSED_BACKGROUND_COLOR = Color.rgb(118, 151, 99);
private static final int BUTTON_RELEASED_BACKGROUND_COLOR = Color.rgb(84, 85, 169);
-
/**
* Input device ID for the touchpad that this debug view is displaying.
*/
@@ -76,24 +75,24 @@ public class TouchpadDebugView extends LinearLayout {
private int mWindowLocationBeforeDragX;
private int mWindowLocationBeforeDragY;
private int mLatestGestureType = 0;
+ private TouchpadSelectionView mTouchpadSelectionView;
+ private TouchpadVisualizationView mTouchpadVisualizationView;
private TextView mGestureInfoView;
- private TextView mNameView;
-
@NonNull
private TouchpadHardwareState mLastTouchpadState =
new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
new TouchpadFingerState[0]);
- private TouchpadVisualizationView mTouchpadVisualizationView;
private final TouchpadHardwareProperties mTouchpadHardwareProperties;
public TouchpadDebugView(Context context, int touchpadId,
- TouchpadHardwareProperties touchpadHardwareProperties) {
+ TouchpadHardwareProperties touchpadHardwareProperties,
+ Consumer<Integer> touchpadSwitchHandler) {
super(context);
mTouchpadId = touchpadId;
mWindowManager =
Objects.requireNonNull(getContext().getSystemService(WindowManager.class));
mTouchpadHardwareProperties = touchpadHardwareProperties;
- init(context, touchpadId);
+ init(context, touchpadId, touchpadSwitchHandler);
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mWindowLayoutParams = new WindowManager.LayoutParams();
@@ -115,7 +114,8 @@ public class TouchpadDebugView extends LinearLayout {
mWindowLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
}
- private void init(Context context, int touchpadId) {
+ private void init(Context context, int touchpadId,
+ Consumer<Integer> touchpadSwitchHandler) {
updateScreenDimensions();
setOrientation(VERTICAL);
setLayoutParams(new LayoutParams(
@@ -123,18 +123,14 @@ public class TouchpadDebugView extends LinearLayout {
LayoutParams.WRAP_CONTENT));
setBackgroundColor(Color.TRANSPARENT);
- mNameView = new TextView(context);
- mNameView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR);
- mNameView.setTextSize(TEXT_SIZE_SP);
- mNameView.setText(Objects.requireNonNull(Objects.requireNonNull(
- mContext.getSystemService(InputManager.class))
- .getInputDevice(touchpadId)).getName());
- mNameView.setGravity(Gravity.CENTER);
- mNameView.setTextColor(Color.WHITE);
+ mTouchpadSelectionView = new TouchpadSelectionView(context,
+ touchpadId, touchpadSwitchHandler);
+ mTouchpadSelectionView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR);
+ mTouchpadSelectionView.setGravity(Gravity.CENTER);
int paddingInDP = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, TEXT_PADDING_DP,
getResources().getDisplayMetrics());
- mNameView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP);
- mNameView.setLayoutParams(
+ mTouchpadSelectionView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP);
+ mTouchpadSelectionView.setLayoutParams(
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
mTouchpadVisualizationView = new TouchpadVisualizationView(context,
@@ -147,10 +143,11 @@ public class TouchpadDebugView extends LinearLayout {
mGestureInfoView.setPadding(paddingInDP, paddingInDP, paddingInDP, paddingInDP);
mGestureInfoView.setLayoutParams(
new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+ //TODO(b/369061237): Handle longer text
updateTheme(getResources().getConfiguration().uiMode);
- addView(mNameView);
+ addView(mTouchpadSelectionView);
addView(mTouchpadVisualizationView);
addView(mGestureInfoView);
@@ -359,12 +356,12 @@ public class TouchpadDebugView extends LinearLayout {
private void onTouchpadButtonPress() {
Slog.d(TAG, "You clicked me!");
- mNameView.setBackgroundColor(BUTTON_PRESSED_BACKGROUND_COLOR);
+ mTouchpadSelectionView.setBackgroundColor(BUTTON_PRESSED_BACKGROUND_COLOR);
}
private void onTouchpadButtonRelease() {
Slog.d(TAG, "You released the click");
- mNameView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR);
+ mTouchpadSelectionView.setBackgroundColor(BUTTON_RELEASED_BACKGROUND_COLOR);
}
/**
diff --git a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
index cb43977d9911..19c802b3c096 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadDebugViewController.java
@@ -45,8 +45,8 @@ public class TouchpadDebugViewController implements InputManager.InputDeviceList
private boolean mTouchpadVisualizerEnabled = false;
public TouchpadDebugViewController(Context context, Looper looper,
- InputManagerService inputManagerService) {
- //TODO(b/363979581): Handle multi-display scenarios
+ InputManagerService inputManagerService) {
+ //TODO(b/369059937): Handle multi-display scenarios
mContext = context;
mHandler = new Handler(looper);
mInputManagerService = inputManagerService;
@@ -77,6 +77,14 @@ public class TouchpadDebugViewController implements InputManager.InputDeviceList
}
}
+ /**
+ * Switch to showing the touchpad with the given device ID
+ */
+ public void switchVisualisationToTouchpadId(int newDeviceId) {
+ if (mTouchpadDebugView != null) hideDebugView(mTouchpadDebugView.getTouchpadId());
+ showDebugView(newDeviceId);
+ }
+
@Override
public void onInputDeviceChanged(int deviceId) {
}
@@ -117,7 +125,7 @@ public class TouchpadDebugViewController implements InputManager.InputDeviceList
touchpadId);
mTouchpadDebugView = new TouchpadDebugView(mContext, touchpadId,
- touchpadHardwareProperties);
+ touchpadHardwareProperties, this::switchVisualisationToTouchpadId);
final WindowManager.LayoutParams mWindowLayoutParams =
mTouchpadDebugView.getWindowLayoutParams();
@@ -150,7 +158,7 @@ public class TouchpadDebugViewController implements InputManager.InputDeviceList
* @param deviceId the deviceId of the touchpad that is sending the hardware state
*/
public void updateTouchpadHardwareState(TouchpadHardwareState touchpadHardwareState,
- int deviceId) {
+ int deviceId) {
if (mTouchpadDebugView != null) {
mTouchpadDebugView.updateHardwareState(touchpadHardwareState, deviceId);
}
diff --git a/services/core/java/com/android/server/input/debug/TouchpadSelectionView.java b/services/core/java/com/android/server/input/debug/TouchpadSelectionView.java
new file mode 100644
index 000000000000..05217b6ab1e0
--- /dev/null
+++ b/services/core/java/com/android/server/input/debug/TouchpadSelectionView.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input.debug;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.hardware.input.InputManager;
+import android.view.Gravity;
+import android.view.InputDevice;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
+import android.widget.TextView;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+
+public class TouchpadSelectionView extends LinearLayout {
+ private static final float TEXT_SIZE_SP = 16.0f;
+
+ int mCurrentTouchpadId;
+
+ public TouchpadSelectionView(Context context, int touchpadId,
+ Consumer<Integer> touchpadSwitchHandler) {
+ super(context);
+ mCurrentTouchpadId = touchpadId;
+ init(context, touchpadSwitchHandler);
+ }
+
+ private void init(Context context, Consumer<Integer> touchpadSwitchHandler) {
+ setOrientation(HORIZONTAL);
+ setLayoutParams(new LayoutParams(
+ LayoutParams.WRAP_CONTENT,
+ LayoutParams.WRAP_CONTENT));
+ setBackgroundColor(Color.TRANSPARENT);
+
+ TextView nameView = new TextView(context);
+ nameView.setTextSize(TEXT_SIZE_SP);
+ nameView.setText(getTouchpadName(mCurrentTouchpadId));
+ nameView.setGravity(Gravity.LEFT);
+ nameView.setTextColor(Color.WHITE);
+
+ LayoutParams textParams = new LayoutParams(
+ LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
+ textParams.rightMargin = 16;
+ nameView.setLayoutParams(textParams);
+
+ ImageButton arrowButton = new ImageButton(context);
+ arrowButton.setImageDrawable(context.getDrawable(android.R.drawable.arrow_down_float));
+ arrowButton.setForegroundGravity(Gravity.RIGHT);
+ arrowButton.setBackgroundColor(Color.TRANSPARENT);
+ arrowButton.setLayoutParams(new LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+
+ arrowButton.setOnClickListener(v -> showPopupMenu(v, context, touchpadSwitchHandler));
+
+ addView(nameView);
+ addView(arrowButton);
+ }
+
+ private void showPopupMenu(View anchorView, Context context,
+ Consumer<Integer> touchpadSwitchHandler) {
+ int i = 0;
+ PopupMenu popupMenu = new PopupMenu(context, anchorView);
+
+ final InputManager inputManager = Objects.requireNonNull(
+ mContext.getSystemService(InputManager.class));
+ for (int deviceId : inputManager.getInputDeviceIds()) {
+ InputDevice inputDevice = inputManager.getInputDevice(deviceId);
+ if (Objects.requireNonNull(inputDevice).supportsSource(
+ InputDevice.SOURCE_TOUCHPAD | InputDevice.SOURCE_MOUSE)) {
+ popupMenu.getMenu().add(0, deviceId, i, getTouchpadName(deviceId));
+ i++;
+ }
+ }
+
+ popupMenu.setOnMenuItemClickListener(item -> {
+ if (item.getItemId() == mCurrentTouchpadId) {
+ return false;
+ }
+
+ touchpadSwitchHandler.accept(item.getItemId());
+ return true;
+ });
+
+ popupMenu.show();
+ }
+
+ private String getTouchpadName(int touchpadId) {
+ return Objects.requireNonNull(Objects.requireNonNull(
+ mContext.getSystemService(InputManager.class))
+ .getInputDevice(touchpadId)).getName();
+ }
+}
diff --git a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
index 96426bbfe4f3..eeec5ccdecf6 100644
--- a/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
+++ b/services/core/java/com/android/server/input/debug/TouchpadVisualizationView.java
@@ -30,6 +30,7 @@ import com.android.server.input.TouchpadHardwareState;
import java.util.ArrayDeque;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
public class TouchpadVisualizationView extends View {
@@ -50,6 +51,7 @@ public class TouchpadVisualizationView extends View {
private final Paint mOvalFillPaint;
private final Paint mTracePaint;
private final Paint mCenterPointPaint;
+ private final Paint mPressureTextPaint;
private final RectF mTempOvalRect = new RectF();
public TouchpadVisualizationView(Context context,
@@ -71,6 +73,8 @@ public class TouchpadVisualizationView extends View {
mCenterPointPaint.setAntiAlias(true);
mCenterPointPaint.setARGB(255, 255, 0, 0);
mCenterPointPaint.setStrokeWidth(2);
+ mPressureTextPaint = new Paint();
+ mPressureTextPaint.setAntiAlias(true);
}
private void removeOldPoints() {
@@ -134,6 +138,13 @@ public class TouchpadVisualizationView extends View {
mOvalFillPaint.setAlpha((int) pressureToOpacity);
drawOval(canvas, newX, newY, newTouchMajor, newTouchMinor, newAngle);
+
+ String formattedPressure = String.format(Locale.getDefault(), "Ps: %.2f",
+ touchpadFingerState.getPressure());
+ float textWidth = mPressureTextPaint.measureText(formattedPressure);
+
+ canvas.drawText(formattedPressure, newX - textWidth / 2,
+ newY - newTouchMajor / 2, mPressureTextPaint);
}
mTempFingerStatesByTrackingId.clear();
@@ -199,6 +210,7 @@ public class TouchpadVisualizationView extends View {
*/
public void setLightModeTheme() {
this.setBackgroundColor(Color.rgb(20, 20, 20));
+ mPressureTextPaint.setARGB(255, 255, 255, 255);
mOvalFillPaint.setARGB(255, 255, 255, 255);
mOvalStrokePaint.setARGB(255, 255, 255, 255);
}
@@ -208,6 +220,7 @@ public class TouchpadVisualizationView extends View {
*/
public void setNightModeTheme() {
this.setBackgroundColor(Color.rgb(240, 240, 240));
+ mPressureTextPaint.setARGB(255, 0, 0, 0);
mOvalFillPaint.setARGB(255, 0, 0, 0);
mOvalStrokePaint.setARGB(255, 0, 0, 0);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index ba7d4d218ca5..b15fcc917588 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -33,6 +33,7 @@ import static android.app.Notification.EXTRA_LARGE_ICON_BIG;
import static android.app.Notification.EXTRA_SUB_TEXT;
import static android.app.Notification.EXTRA_TEXT;
import static android.app.Notification.EXTRA_TEXT_LINES;
+import static android.app.Notification.EXTRA_TITLE;
import static android.app.Notification.EXTRA_TITLE_BIG;
import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY;
import static android.app.Notification.FLAG_AUTO_CANCEL;
@@ -45,6 +46,7 @@ import static android.app.Notification.FLAG_NO_CLEAR;
import static android.app.Notification.FLAG_NO_DISMISS;
import static android.app.Notification.FLAG_ONGOING_EVENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
+import static android.app.Notification.FLAG_PROMOTED_ONGOING;
import static android.app.Notification.FLAG_USER_INITIATED_JOB;
import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
import static android.app.NotificationChannel.NEWS_ID;
@@ -3516,7 +3518,7 @@ public class NotificationManagerService extends SystemService {
private String getHistoryTitle(Notification n) {
CharSequence title = null;
if (n.extras != null) {
- title = n.extras.getCharSequence(Notification.EXTRA_TITLE);
+ title = n.extras.getCharSequence(EXTRA_TITLE);
if (title == null) {
title = n.extras.getCharSequence(EXTRA_TITLE_BIG);
}
@@ -4114,6 +4116,75 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public boolean canBePromoted(String pkg, int uid) {
+ checkCallerIsSystemOrSystemUiOrShell();
+ if (!android.app.Flags.uiRichOngoing()) {
+ return false;
+ }
+ return mPreferencesHelper.canBePromoted(pkg, uid);
+ }
+
+ @Override
+ @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void setCanBePromoted(String pkg, int uid, boolean promote) {
+ checkCallerIsSystemOrSystemUiOrShell();
+ if (!android.app.Flags.uiRichOngoing()) {
+ return;
+ }
+ boolean changed = mPreferencesHelper.setCanBePromoted(pkg, uid, promote);
+ if (changed) {
+ // check for pending/posted notifs from this app and update the flag
+ synchronized (mNotificationLock) {
+ // for enqueued we just need to update the flag
+ List<NotificationRecord> enqueued = findAppNotificationByListLocked(
+ mEnqueuedNotifications, pkg, UserHandle.getUserId(uid));
+ for (NotificationRecord r : enqueued) {
+ if (promote
+ && r.getNotification().hasPromotableCharacteristics()
+ && r.getImportance() > IMPORTANCE_MIN) {
+ r.getNotification().flags |= FLAG_PROMOTED_ONGOING;
+ } else if (!promote) {
+ r.getNotification().flags &= ~FLAG_PROMOTED_ONGOING;
+ }
+ }
+ // if the notification is posted we need to update the flag and tell listeners
+ List<NotificationRecord> posted = findAppNotificationByListLocked(
+ mNotificationList, pkg, UserHandle.getUserId(uid));
+ for (NotificationRecord r : posted) {
+ if (promote
+ && !hasFlag(r.getNotification().flags, FLAG_PROMOTED_ONGOING)
+ && r.getNotification().hasPromotableCharacteristics()
+ && r.getImportance() > IMPORTANCE_MIN) {
+ r.getNotification().flags |= FLAG_PROMOTED_ONGOING;
+ // we could set a wake lock here but this value should only change
+ // in response to user action, so the device should be awake long enough
+ // to post
+ PostNotificationTracker tracker =
+ mPostNotificationTrackerFactory.newTracker(null);
+ // Set false for isAppForeground because that field is only used
+ // for bubbles and messagingstyle can not be promoted
+ mHandler.post(new EnqueueNotificationRunnable(
+ r.getUser().getIdentifier(),
+ r, /* isAppForeground */ false, /* isAppProvided= */ false,
+ tracker));
+ } else if (!promote
+ && hasFlag(r.getNotification().flags, FLAG_PROMOTED_ONGOING)){
+ r.getNotification().flags &= ~FLAG_PROMOTED_ONGOING;
+ PostNotificationTracker tracker =
+ mPostNotificationTrackerFactory.newTracker(null);
+ mHandler.post(new EnqueueNotificationRunnable(
+ r.getUser().getIdentifier(),
+ r, /* isAppForeground */ false, /* isAppProvided= */ false,
+ tracker));
+ }
+ }
+ }
+ handleSavePolicyFile();
+ }
+ }
+
+ @Override
public boolean hasSentValidMsg(String pkg, int uid) {
checkCallerIsSystem();
return mPreferencesHelper.hasSentValidMsg(pkg, uid);
@@ -7698,6 +7769,16 @@ public class NotificationManagerService extends SystemService {
return false;
}
+ if (android.app.Flags.uiRichOngoing()) {
+ // This would normally be done in fixNotification(), but we need the channel info so
+ // it's done a little late
+ if (mPreferencesHelper.canBePromoted(pkg, notificationUid)
+ && notification.hasPromotableCharacteristics()
+ && channel.getImportance() > IMPORTANCE_MIN) {
+ notification.flags |= FLAG_PROMOTED_ONGOING;
+ }
+ }
+
final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
r.setIsAppImportanceLocked(mPermissionHelper.isPermissionUserSet(pkg, userId));
r.setPostSilently(postSilently);
@@ -7938,6 +8019,9 @@ public class NotificationManagerService extends SystemService {
}
}
+ // Apps cannot set this flag
+ notification.flags &= ~FLAG_PROMOTED_ONGOING;
+
// Ensure CallStyle has all the correct actions
if (notification.isStyle(Notification.CallStyle.class)) {
Notification.Builder builder =
@@ -8061,12 +8145,7 @@ public class NotificationManagerService extends SystemService {
private void checkRemoteViews(String pkg, String tag, int id, Notification notification) {
if (android.app.Flags.removeRemoteViews()) {
- if (notification.contentView != null || notification.bigContentView != null
- || notification.headsUpContentView != null
- || (notification.publicVersion != null
- && (notification.publicVersion.contentView != null
- || notification.publicVersion.bigContentView != null
- || notification.publicVersion.headsUpContentView != null))) {
+ if (notification.containsCustomViews()) {
Slog.i(TAG, "Removed customViews for " + pkg);
mUsageStats.registerImageRemoved(pkg);
}
@@ -9236,8 +9315,8 @@ public class NotificationManagerService extends SystemService {
}
}
- final String oldTitle = String.valueOf(oldN.extras.get(Notification.EXTRA_TITLE));
- final String newTitle = String.valueOf(newN.extras.get(Notification.EXTRA_TITLE));
+ final String oldTitle = String.valueOf(oldN.extras.get(EXTRA_TITLE));
+ final String newTitle = String.valueOf(newN.extras.get(EXTRA_TITLE));
if (!Objects.equals(oldTitle, newTitle)) {
if (DEBUG_INTERRUPTIVENESS) {
Slog.v(TAG, "INTERRUPTIVENESS: "
@@ -10654,6 +10733,22 @@ public class NotificationManagerService extends SystemService {
}
@GuardedBy("mNotificationLock")
+ @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ private @NonNull List<NotificationRecord> findAppNotificationByListLocked(
+ ArrayList<NotificationRecord> list, String pkg, int userId) {
+ List<NotificationRecord> records = new ArrayList<>();
+ final int len = list.size();
+ for (int i = 0; i < len; i++) {
+ NotificationRecord r = list.get(i);
+ if (notificationMatchesUserId(r, userId, false)
+ && r.getSbn().getPackageName().equals(pkg)) {
+ records.add(r);
+ }
+ }
+ return records;
+ }
+
+ @GuardedBy("mNotificationLock")
private @NonNull List<NotificationRecord> findGroupNotificationByListLocked(
ArrayList<NotificationRecord> list, String pkg, String groupKey, int userId) {
List<NotificationRecord> records = new ArrayList<>();
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index a4fdb758a740..fcc8d2f74ce9 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -41,6 +41,7 @@ import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_P
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__GRANTED;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES__FSI_STATE__NOT_REQUESTED;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -162,6 +163,7 @@ public class PreferencesHelper implements RankingConfig {
private static final String ATT_SENT_VALID_MESSAGE = "sent_valid_msg";
private static final String ATT_USER_DEMOTED_INVALID_MSG_APP = "user_demote_msg_app";
private static final String ATT_SENT_VALID_BUBBLE = "sent_valid_bubble";
+ private static final String ATT_PROMOTE_NOTIFS = "promote";
private static final String ATT_CREATION_TIME = "creation_time";
@@ -351,6 +353,10 @@ public class PreferencesHelper implements RankingConfig {
r.userDemotedMsgApp = parser.getAttributeBoolean(
null, ATT_USER_DEMOTED_INVALID_MSG_APP, false);
r.hasSentValidBubble = parser.getAttributeBoolean(null, ATT_SENT_VALID_BUBBLE, false);
+ if (android.app.Flags.uiRichOngoing()) {
+ r.canHavePromotedNotifs =
+ parser.getAttributeBoolean(null, ATT_PROMOTE_NOTIFS, false);
+ }
final int innerDepth = parser.getDepth();
int type;
@@ -739,6 +745,11 @@ public class PreferencesHelper implements RankingConfig {
out.attributeBoolean(null, ATT_USER_DEMOTED_INVALID_MSG_APP,
r.userDemotedMsgApp);
out.attributeBoolean(null, ATT_SENT_VALID_BUBBLE, r.hasSentValidBubble);
+ if (android.app.Flags.uiRichOngoing()) {
+ if (r.canHavePromotedNotifs) {
+ out.attributeBoolean(null, ATT_PROMOTE_NOTIFS, r.canHavePromotedNotifs);
+ }
+ }
if (Flags.persistIncompleteRestoreData() && r.uid == UNKNOWN_UID) {
out.attributeLong(null, ATT_CREATION_TIME, r.creationTime);
@@ -839,6 +850,28 @@ public class PreferencesHelper implements RankingConfig {
}
}
+ @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public boolean canBePromoted(String packageName, int uid) {
+ synchronized (mLock) {
+ return getOrCreatePackagePreferencesLocked(packageName, uid).canHavePromotedNotifs;
+ }
+ }
+
+ @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public boolean setCanBePromoted(String packageName, int uid, boolean promote) {
+ boolean changed = false;
+ synchronized (mLock) {
+ PackagePreferences pkgPrefs = getOrCreatePackagePreferencesLocked(packageName, uid);
+ if (pkgPrefs.canHavePromotedNotifs != promote) {
+ pkgPrefs.canHavePromotedNotifs = promote;
+ changed = true;
+ }
+ }
+ // no need to send a ranking update because we need to update the flag value on all pending
+ // and posted notifs and NMS will take care of that
+ return changed;
+ }
+
public boolean isInInvalidMsgState(String packageName, int uid) {
synchronized (mLock) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid);
@@ -2180,6 +2213,10 @@ public class PreferencesHelper implements RankingConfig {
pw.print(" fixedImportance=");
pw.print(r.fixedImportance);
}
+ if (android.app.Flags.uiRichOngoing() && r.canHavePromotedNotifs) {
+ pw.print(" promoted=");
+ pw.print(r.canHavePromotedNotifs);
+ }
pw.println();
for (NotificationChannel channel : r.channels.values()) {
pw.print(prefix);
@@ -3028,6 +3065,9 @@ public class PreferencesHelper implements RankingConfig {
boolean migrateToPm = false;
long creationTime;
+ @FlaggedApi(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ boolean canHavePromotedNotifs = false;
+
@UserIdInt int userId;
Delegate delegate = null;
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 0eb4cbda72bf..626c3ddd49d9 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -1722,7 +1722,7 @@ public class ZenModeHelper {
// booleans to determine whether to reset the rules to the default rules
boolean allRulesDisabled = true;
boolean hasDefaultRules = config.automaticRules.containsAll(
- ZenModeConfig.DEFAULT_RULE_IDS);
+ ZenModeConfig.getDefaultRuleIds());
long time = Flags.modesApi() ? mClock.millis() : System.currentTimeMillis();
if (config.automaticRules != null && config.automaticRules.size() > 0) {
@@ -1799,6 +1799,14 @@ public class ZenModeHelper {
config.deletedRules.clear();
}
+ if (Flags.modesUi() && config.automaticRules != null) {
+ ZenRule obsoleteEventsRule = config.automaticRules.get(
+ ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
+ if (obsoleteEventsRule != null && !obsoleteEventsRule.enabled) {
+ config.automaticRules.remove(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
+ }
+ }
+
if (DEBUG) Log.d(TAG, reason);
synchronized (mConfigLock) {
setConfigLocked(config, null,
@@ -2257,7 +2265,7 @@ public class ZenModeHelper {
private static void updateRuleStringsForCurrentLocale(Context context,
ZenModeConfig defaultConfig) {
for (ZenRule rule : defaultConfig.automaticRules.values()) {
- if (ZenModeConfig.EVENTS_DEFAULT_RULE_ID.equals(rule.id)) {
+ if (ZenModeConfig.EVENTS_OBSOLETE_RULE_ID.equals(rule.id)) {
rule.name = context.getResources()
.getString(R.string.zen_mode_default_events_name);
} else if (ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID.equals(rule.id)) {
@@ -2279,7 +2287,7 @@ public class ZenModeHelper {
}
ZenPolicy defaultPolicy = defaultConfig.getZenPolicy();
for (ZenRule rule : defaultConfig.automaticRules.values()) {
- if (ZenModeConfig.DEFAULT_RULE_IDS.contains(rule.id) && rule.zenPolicy == null) {
+ if (ZenModeConfig.getDefaultRuleIds().contains(rule.id) && rule.zenPolicy == null) {
rule.zenPolicy = defaultPolicy.copy();
}
}
@@ -2483,7 +2491,7 @@ public class ZenModeHelper {
List<StatsEvent> events) {
// Make the ID safe.
String id = rule.id == null ? "" : rule.id;
- if (!ZenModeConfig.DEFAULT_RULE_IDS.contains(id)) {
+ if (!ZenModeConfig.getDefaultRuleIds().contains(id)) {
id = "";
}
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index e3d71e4998be..f78c4488cbfb 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -81,7 +81,7 @@ import java.util.function.Consumer;
public final class RollbackPackageHealthObserver implements PackageHealthObserver {
private static final String TAG = "RollbackPackageHealthObserver";
private static final String NAME = "rollback-observer";
- private static final String ACTION_NAME = RollbackPackageHealthObserver.class.getName();
+ private static final String CLASS_NAME = RollbackPackageHealthObserver.class.getName();
private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
| ApplicationInfo.FLAG_SYSTEM;
@@ -610,14 +610,16 @@ public final class RollbackPackageHealthObserver implements PackageHealthObserve
}
};
+ String intentActionName = CLASS_NAME + rollback.getRollbackId();
// Register the BroadcastReceiver
mContext.registerReceiver(rollbackReceiver,
- new IntentFilter(ACTION_NAME),
+ new IntentFilter(intentActionName),
Context.RECEIVER_NOT_EXPORTED);
- Intent intentReceiver = new Intent(ACTION_NAME);
+ Intent intentReceiver = new Intent(intentActionName);
intentReceiver.putExtra("rollbackId", rollback.getRollbackId());
intentReceiver.setPackage(mContext.getPackageName());
+ intentReceiver.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
PendingIntent rollbackPendingIntent = PendingIntent.getBroadcast(mContext,
rollback.getRollbackId(),
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ccc9b17ff840..12d733fc8c1a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2641,9 +2641,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return true;
}
// Only do transfer after transaction has done when starting window exist.
- if (mStartingData != null && mStartingData.mWaitForSyncTransactionCommit) {
- mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT;
- return true;
+ if (mStartingData != null) {
+ final boolean isWaitingForSyncTransactionCommit =
+ Flags.removeStartingWindowWaitForMultiTransitions()
+ ? getSyncTransactionCommitCallbackDepth() > 0
+ : mStartingData.mWaitForSyncTransactionCommit;
+ if (isWaitingForSyncTransactionCommit) {
+ mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_COPY_TO_CLIENT;
+ return true;
+ }
}
requestCopySplashScreen();
return isTransferringSplashScreen();
@@ -2847,7 +2853,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final boolean animate;
final boolean hasImeSurface;
if (mStartingData != null) {
- if (mStartingData.mWaitForSyncTransactionCommit
+ final boolean isWaitingForSyncTransactionCommit =
+ Flags.removeStartingWindowWaitForMultiTransitions()
+ ? getSyncTransactionCommitCallbackDepth() > 0
+ : mStartingData.mWaitForSyncTransactionCommit;
+ if (isWaitingForSyncTransactionCommit
|| mSyncState != SYNC_STATE_NONE) {
mStartingData.mRemoveAfterTransaction = AFTER_TRANSACTION_REMOVE_DIRECTLY;
mStartingData.mPrepareRemoveAnimation = prepareAnimation;
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index f0a4763796e3..57b879277326 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -75,7 +75,8 @@ class InsetsSourceProvider {
private final Rect mTmpRect = new Rect();
private final InsetsSourceControl mFakeControl;
- private final Consumer<Transaction> mSetLeashPositionConsumer;
+ private final Point mPosition = new Point();
+ private final Consumer<Transaction> mSetControlPositionConsumer;
private @Nullable InsetsControlTarget mPendingControlTarget;
private @Nullable InsetsControlTarget mFakeControlTarget;
@@ -126,13 +127,14 @@ class InsetsSourceProvider {
source.getId(), source.getType(), null /* leash */, false /* initialVisible */,
new Point(), Insets.NONE);
mControllable = (InsetsPolicy.CONTROLLABLE_TYPES & source.getType()) != 0;
- mSetLeashPositionConsumer = t -> {
- if (mControl != null) {
- final SurfaceControl leash = mControl.getLeash();
- if (leash != null) {
- final Point position = mControl.getSurfacePosition();
- t.setPosition(leash, position.x, position.y);
- }
+ mSetControlPositionConsumer = t -> {
+ if (mControl == null || mControlTarget == null) {
+ return;
+ }
+ boolean changed = mControl.setSurfacePosition(mPosition.x, mPosition.y);
+ final SurfaceControl leash = mControl.getLeash();
+ if (changed && leash != null) {
+ t.setPosition(leash, mPosition.x, mPosition.y);
}
if (mHasPendingPosition) {
mHasPendingPosition = false;
@@ -140,9 +142,22 @@ class InsetsSourceProvider {
mStateController.notifyControlTargetChanged(mPendingControlTarget, this);
}
}
+ changed |= updateInsetsHint();
+ if (changed) {
+ mStateController.notifyControlChanged(mControlTarget, this);
+ }
};
}
+ private boolean updateInsetsHint() {
+ final Insets insetsHint = getInsetsHint();
+ if (!mControl.getInsetsHint().equals(insetsHint)) {
+ mControl.setInsetsHint(insetsHint);
+ return true;
+ }
+ return false;
+ }
+
InsetsSource getSource() {
return mSource;
}
@@ -363,26 +378,32 @@ class InsetsSourceProvider {
}
final boolean serverVisibleChanged = mServerVisible != isServerVisible;
setServerVisible(isServerVisible);
- updateInsetsControlPosition(windowState, serverVisibleChanged);
- }
-
- void updateInsetsControlPosition(WindowState windowState) {
- updateInsetsControlPosition(windowState, false);
+ final boolean positionChanged = updateInsetsControlPosition(windowState);
+ if (mControl != null && !positionChanged
+ // The insets hint would be updated if the position is changed. Here updates it for
+ // the possible change of the bounds or the server visibility.
+ && (updateInsetsHint()
+ || serverVisibleChanged
+ && android.view.inputmethod.Flags.refactorInsetsController())) {
+ // Only call notifyControlChanged here when the position is not changed. Otherwise, it
+ // is called or is scheduled to be called during updateInsetsControlPosition.
+ mStateController.notifyControlChanged(mControlTarget, this);
+ }
}
- private void updateInsetsControlPosition(WindowState windowState,
- boolean serverVisibleChanged) {
+ /**
+ * @return {#code true} if the surface position of the control is changed.
+ */
+ boolean updateInsetsControlPosition(WindowState windowState) {
if (mControl == null) {
- return;
+ return false;
}
- boolean changed = false;
final Point position = getWindowFrameSurfacePosition();
- if (mControl.setSurfacePosition(position.x, position.y) && mControlTarget != null) {
- changed = true;
+ if (!mPosition.equals(position)) {
+ mPosition.set(position.x, position.y);
if (windowState != null && windowState.getWindowFrames().didFrameSizeChange()
&& windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) {
- mHasPendingPosition = true;
- windowState.applyWithNextDraw(mSetLeashPositionConsumer);
+ windowState.applyWithNextDraw(mSetControlPositionConsumer);
} else {
Transaction t = mWindowContainer.getSyncTransaction();
if (windowState != null) {
@@ -399,20 +420,11 @@ class InsetsSourceProvider {
}
}
}
- mSetLeashPositionConsumer.accept(t);
+ mSetControlPositionConsumer.accept(t);
}
+ return true;
}
- final Insets insetsHint = getInsetsHint();
- if (!mControl.getInsetsHint().equals(insetsHint)) {
- mControl.setInsetsHint(insetsHint);
- changed = true;
- }
- if (android.view.inputmethod.Flags.refactorInsetsController() && serverVisibleChanged) {
- changed = true;
- }
- if (changed) {
- mStateController.notifyControlChanged(mControlTarget, this);
- }
+ return false;
}
private Point getWindowFrameSurfacePosition() {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 5550f3efaa3a..d295378a9b65 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -699,8 +699,10 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
final WindowState win = mService.windowForClientLocked(this, window,
false /* throwOnError */);
if (win != null) {
- ImeTracker.forLogging().onProgress(imeStatsToken,
- ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
+ if (android.view.inputmethod.Flags.refactorInsetsController()) {
+ ImeTracker.forLogging().onProgress(imeStatsToken,
+ ImeTracker.PHASE_WM_UPDATE_REQUESTED_VISIBLE_TYPES);
+ }
win.setRequestedVisibleTypes(requestedVisibleTypes);
win.getDisplayContent().getInsetsPolicy().onRequestedVisibleTypesChanged(win,
imeStatsToken);
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index 24fb20731c43..896612d3d27a 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -68,7 +68,9 @@ public abstract class StartingData {
* window.
* Note this isn't equal to transition playing, the period should be
* Sync finishNow -> Start transaction apply.
+ * @deprecated TODO(b/362347290): cleanup after fix ramp up
*/
+ @Deprecated
boolean mWaitForSyncTransactionCommit;
/**
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 92953e5a5041..83e714d82dd2 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -429,7 +429,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
}
final IBinder activityToken;
- if (activity.getPid() == mOrganizerPid) {
+ if (activity.getPid() == mOrganizerPid && activity.getUid() == mOrganizerUid) {
// We only pass the actual token if the activity belongs to the organizer process.
activityToken = activity.token;
} else {
@@ -458,7 +458,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
change.setTaskFragmentToken(lastParentTfToken);
}
// Only pass the activity token to the client if it belongs to the same process.
- if (nextFillTaskActivity != null && nextFillTaskActivity.getPid() == mOrganizerPid) {
+ if (nextFillTaskActivity != null && nextFillTaskActivity.getPid() == mOrganizerPid
+ && nextFillTaskActivity.getUid() == mOrganizerUid) {
change.setOtherActivityToken(nextFillTaskActivity.token);
}
return change;
@@ -553,6 +554,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
"Replacing existing organizer currently unsupported");
}
+ if (pid <= 0) {
+ throw new IllegalStateException("Cannot register from invalid pid: " + pid);
+ }
+
if (restoreFromCachedStateIfPossible(organizer, pid, uid, outSavedState)) {
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 0a9cb1c38dab..1c03ba571923 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -4351,4 +4351,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
t.merge(mSyncTransaction);
}
+ int getSyncTransactionCommitCallbackDepth() {
+ return mSyncTransactionCommitCallbackDepth;
+ }
}
diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
index c05c3819ca28..bc64e158e830 100644
--- a/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
+++ b/services/tests/appfunctions/src/com/android/server/appfunctions/MetadataSyncAdapterTest.kt
@@ -36,7 +36,6 @@ import androidx.test.platform.app.InstrumentationRegistry
import com.android.internal.infra.AndroidFuture
import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults
import com.google.common.truth.Truth.assertThat
-import com.google.common.util.concurrent.MoreExecutors
import java.util.concurrent.atomic.AtomicBoolean
import org.junit.Test
import org.junit.runner.RunWith
@@ -46,7 +45,6 @@ import org.junit.runners.JUnit4
class MetadataSyncAdapterTest {
private val context = InstrumentationRegistry.getInstrumentation().targetContext
private val appSearchManager = context.getSystemService(AppSearchManager::class.java)
- private val testExecutor = MoreExecutors.directExecutor()
private val packageManager = context.packageManager
@Test
@@ -138,8 +136,7 @@ class MetadataSyncAdapterTest {
PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
runtimeSearchSession.put(putDocumentsRequest).get()
staticSearchSession.put(putDocumentsRequest).get()
- val metadataSyncAdapter =
- MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+ val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
val submitSyncRequest =
metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
@@ -180,8 +177,7 @@ class MetadataSyncAdapterTest {
val putDocumentsRequest: PutDocumentsRequest =
PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
staticSearchSession.put(putDocumentsRequest).get()
- val metadataSyncAdapter =
- MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+ val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
val submitSyncRequest =
metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
@@ -236,8 +232,7 @@ class MetadataSyncAdapterTest {
val putDocumentsRequest: PutDocumentsRequest =
PutDocumentsRequest.Builder().addGenericDocuments(functionRuntimeMetadata).build()
runtimeSearchSession.put(putDocumentsRequest).get()
- val metadataSyncAdapter =
- MetadataSyncAdapter(testExecutor, packageManager, appSearchManager)
+ val metadataSyncAdapter = MetadataSyncAdapter(packageManager, appSearchManager)
val submitSyncRequest =
metadataSyncAdapter.trySyncAppFunctionMetadataBlocking(
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 b8f9767b5512..130690d80b70 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -38,6 +38,7 @@ import static android.app.Notification.FLAG_NO_CLEAR;
import static android.app.Notification.FLAG_NO_DISMISS;
import static android.app.Notification.FLAG_ONGOING_EVENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
+import static android.app.Notification.FLAG_PROMOTED_ONGOING;
import static android.app.Notification.FLAG_USER_INITIATED_JOB;
import static android.app.Notification.GROUP_ALERT_CHILDREN;
import static android.app.Notification.VISIBILITY_PRIVATE;
@@ -53,6 +54,7 @@ import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.app.NotificationManager.IMPORTANCE_MAX;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.IMPORTANCE_NONE;
import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS;
@@ -468,6 +470,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationChannel mSilentChannel = new NotificationChannel("low", "low", IMPORTANCE_LOW);
+ NotificationChannel mMinChannel = new NotificationChannel("min", "min", IMPORTANCE_MIN);
+
private static final int NOTIFICATION_LOCATION_UNKNOWN = 0;
private static final String VALID_CONVO_SHORTCUT_ID = "shortcut";
@@ -558,8 +562,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Parameters(name = "{0}")
public static List<FlagsParameterization> getParams() {
- return FlagsParameterization.allCombinationsOf(
- FLAG_ALL_NOTIFS_NEED_TTL);
+ return FlagsParameterization.allCombinationsOf();
}
public NotificationManagerServiceTest(FlagsParameterization flags) {
@@ -856,15 +859,17 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mInternalService = mService.getInternalService();
mBinderService.createNotificationChannels(mPkg, new ParceledListSlice(
- Arrays.asList(mTestNotificationChannel, mSilentChannel)));
+ Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel)));
mBinderService.createNotificationChannels(PKG_P, new ParceledListSlice(
- Arrays.asList(mTestNotificationChannel, mSilentChannel)));
+ Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel)));
mBinderService.createNotificationChannels(PKG_O, new ParceledListSlice(
- Arrays.asList(mTestNotificationChannel, mSilentChannel)));
+ Arrays.asList(mTestNotificationChannel, mSilentChannel, mMinChannel)));
assertNotNull(mBinderService.getNotificationChannel(
mPkg, mContext.getUserId(), mPkg, TEST_CHANNEL_ID));
assertNotNull(mBinderService.getNotificationChannel(
mPkg, mContext.getUserId(), mPkg, mSilentChannel.getId()));
+ assertNotNull(mBinderService.getNotificationChannel(
+ mPkg, mContext.getUserId(), mPkg, mMinChannel.getId()));
clearInvocations(mRankingHandler);
when(mPermissionHelper.hasPermission(mUid)).thenReturn(true);
@@ -943,6 +948,16 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
}
+ private ShortcutInfo createMockConvoShortcut() {
+ ShortcutInfo info = mock(ShortcutInfo.class);
+ when(info.getPackage()).thenReturn(mPkg);
+ when(info.getId()).thenReturn(VALID_CONVO_SHORTCUT_ID);
+ when(info.getUserId()).thenReturn(USER_SYSTEM);
+ when(info.isLongLived()).thenReturn(true);
+ when(info.isEnabled()).thenReturn(true);
+ return info;
+ }
+
private void simulatePackageSuspendBroadcast(boolean suspend, String pkg,
int uid) {
// mimics receive broadcast that package is (un)suspended
@@ -16540,13 +16555,298 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
assertThat(r.getChannel().getId()).isEqualTo(NEWS_ID);
}
- private ShortcutInfo createMockConvoShortcut() {
- ShortcutInfo info = mock(ShortcutInfo.class);
- when(info.getPackage()).thenReturn(mPkg);
- when(info.getId()).thenReturn(VALID_CONVO_SHORTCUT_ID);
- when(info.getUserId()).thenReturn(USER_SYSTEM);
- when(info.isLongLived()).thenReturn(true);
- when(info.isEnabled()).thenReturn(true);
- return info;
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void testSetCanBePromoted_granted() throws Exception {
+ mContext.getTestablePermissions().setPermission(
+ android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+ // qualifying posted notification
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+ .build();
+
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ // qualifying enqueued notification
+ Notification n1 = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+ .build();
+ StatusBarNotification sbn1 = new StatusBarNotification(mPkg, mPkg, 7, null, mUid, 0,
+ n1, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mTestNotificationChannel);
+
+ // another package but otherwise would qualify
+ Notification n2 = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+ .build();
+ StatusBarNotification sbn2 = new StatusBarNotification(PKG_O, PKG_O, 7, null, UID_O, 0,
+ n2, UserHandle.getUserHandleForUid(UID_O), null, 0);
+ NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mTestNotificationChannel);
+
+ // not-qualifying posted notification
+ Notification n3 = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .build();
+
+ StatusBarNotification sbn3 = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0,
+ n3, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r3 = new NotificationRecord(mContext, sbn3, mTestNotificationChannel);
+
+ mService.addNotification(r3);
+ mService.addNotification(r2);
+ mService.addNotification(r);
+ mService.addEnqueuedNotification(r1);
+
+ mBinderService.setCanBePromoted(mPkg, mUid, true);
+
+ waitForIdle();
+
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).prepareNotifyPostedLocked(
+ captor.capture(), any(), anyBoolean());
+
+ // the posted one
+ assertThat(mService.hasFlag(captor.getValue().getNotification().flags,
+ FLAG_PROMOTED_ONGOING)).isTrue();
+ // the enqueued one
+ assertThat(mService.hasFlag(r1.getNotification().flags, FLAG_PROMOTED_ONGOING)).isTrue();
+ // the other app
+ assertThat(mService.hasFlag(r2.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse();
+ // same app, not qualifying
+ assertThat(mService.hasFlag(r3.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void testSetCanBePromoted_granted_onlyNotifiesOnce() throws Exception {
+ mContext.getTestablePermissions().setPermission(
+ android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+ // qualifying posted notification
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+ .build();
+
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mService.addNotification(r);
+
+ mBinderService.setCanBePromoted(mPkg, mUid, true);
+ waitForIdle();
+ mBinderService.setCanBePromoted(mPkg, mUid, true);
+ waitForIdle();
+
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).prepareNotifyPostedLocked(
+ captor.capture(), any(), anyBoolean());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void testSetCanBePromoted_revoked() throws Exception {
+ mContext.getTestablePermissions().setPermission(
+ android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+ // start from true state
+ mBinderService.setCanBePromoted(mPkg, mUid, true);
+
+ // qualifying posted notification
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post
+ .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+ .build();
+
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ // qualifying enqueued notification
+ Notification n1 = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post
+ .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+ .build();
+ StatusBarNotification sbn1 = new StatusBarNotification(mPkg, mPkg, 7, null, mUid, 0,
+ n1, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r1 = new NotificationRecord(mContext, sbn1, mTestNotificationChannel);
+
+ // doesn't qualify, same package
+ Notification n2 = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .build();
+ StatusBarNotification sbn2 = new StatusBarNotification(mPkg, mPkg, 8, null, mUid, 0,
+ n2, UserHandle.getUserHandleForUid(UID_O), null, 0);
+ NotificationRecord r2 = new NotificationRecord(mContext, sbn2, mTestNotificationChannel);
+
+ mService.addNotification(r2);
+ mService.addNotification(r);
+ mService.addEnqueuedNotification(r1);
+
+ mBinderService.setCanBePromoted(mPkg, mUid, false);
+
+ waitForIdle();
+
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).prepareNotifyPostedLocked(
+ captor.capture(), any(), anyBoolean());
+
+ // the posted one
+ assertThat(mService.hasFlag(captor.getValue().getNotification().flags,
+ FLAG_PROMOTED_ONGOING)).isFalse();
+ // the enqueued one
+ assertThat(mService.hasFlag(r1.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse();
+ // the not qualifying one
+ assertThat(mService.hasFlag(r2.getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void testSetCanBePromoted_revoked_onlyNotifiesOnce() throws Exception {
+ mContext.getTestablePermissions().setPermission(
+ android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+ // start from true state
+ mBinderService.setCanBePromoted(mPkg, mUid, true);
+
+ // qualifying posted notification
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .setFlag(FLAG_PROMOTED_ONGOING, true) // add manually since we're skipping post
+ .setFlag(FLAG_CAN_COLORIZE, true) // add manually since we're skipping post
+ .build();
+
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+
+ mService.addNotification(r);
+
+ mBinderService.setCanBePromoted(mPkg, mUid, false);
+ waitForIdle();
+ mBinderService.setCanBePromoted(mPkg, mUid, false);
+ waitForIdle();
+
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).prepareNotifyPostedLocked(
+ captor.capture(), any(), anyBoolean());
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void testPostPromotableNotification() throws Exception {
+ mBinderService.setCanBePromoted(mPkg, mUid, true);
+ assertThat(mBinderService.canBePromoted(mPkg, mUid)).isTrue();
+ mContext.getTestablePermissions().setPermission(
+ android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .build();
+ //assertThat(n.hasPromotableCharacteristics()).isTrue();
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(),
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ waitForIdle();
+
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).prepareNotifyPostedLocked(
+ captor.capture(), any(), anyBoolean());
+
+ assertThat(mService.hasFlag(captor.getValue().getNotification().flags,
+ FLAG_PROMOTED_ONGOING)).isTrue();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void testPostPromotableNotification_noPermission() throws Exception {
+ mContext.getTestablePermissions().setPermission(
+ android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+ Notification n = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .build();
+
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(),
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ waitForIdle();
+
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).prepareNotifyPostedLocked(
+ captor.capture(), any(), anyBoolean());
+
+ assertThat(mService.hasFlag(captor.getValue().getNotification().flags,
+ FLAG_PROMOTED_ONGOING)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void testPostPromotableNotification_unimportantNotification() throws Exception {
+ mBinderService.setCanBePromoted(mPkg, mUid, true);
+ mContext.getTestablePermissions().setPermission(
+ android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, PERMISSION_GRANTED);
+ Notification n = new Notification.Builder(mContext, mMinChannel.getId())
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setStyle(new Notification.BigTextStyle().setBigContentTitle("BIG"))
+ .setColor(Color.WHITE)
+ .setColorized(true)
+ .build();
+
+ StatusBarNotification sbn = new StatusBarNotification(mPkg, mPkg, 9, null, mUid, 0,
+ n, UserHandle.getUserHandleForUid(mUid), null, 0);
+
+ mBinderService.enqueueNotificationWithTag(mPkg, mPkg, sbn.getTag(),
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
+ waitForIdle();
+
+ ArgumentCaptor<NotificationRecord> captor =
+ ArgumentCaptor.forClass(NotificationRecord.class);
+ verify(mListeners, times(1)).prepareNotifyPostedLocked(
+ captor.capture(), any(), anyBoolean());
+
+ assertThat(mService.hasFlag(captor.getValue().getNotification().flags,
+ FLAG_PROMOTED_ONGOING)).isFalse();
}
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 1905ae4aec4b..7d63062784f9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -518,6 +518,17 @@ public class PreferencesHelperTest extends UiServiceTestCase {
doneLatch.await();
}
+ private static NotificationChannel cloneChannel(NotificationChannel original) {
+ Parcel parcel = Parcel.obtain();
+ try {
+ original.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ return NotificationChannel.CREATOR.createFromParcel(parcel);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
@Test
public void testWriteXml_onlyBackupsTargetUser() throws Exception {
// Setup package notifications.
@@ -631,6 +642,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}
mHelper.setShowBadge(PKG_N_MR1, UID_N_MR1, true);
+ if (android.app.Flags.uiRichOngoing()) {
+ mHelper.setCanBePromoted(PKG_N_MR1, UID_N_MR1, true);
+ }
ByteArrayOutputStream baos = writeXmlAndPurge(PKG_N_MR1, UID_N_MR1, false,
UserHandle.USER_ALL, channel1.getId(), channel2.getId(),
@@ -641,6 +655,9 @@ public class PreferencesHelperTest extends UiServiceTestCase {
loadStreamXml(baos, false, UserHandle.USER_ALL);
assertTrue(mXmlHelper.canShowBadge(PKG_N_MR1, UID_N_MR1));
+ if (android.app.Flags.uiRichOngoing()) {
+ assertThat(mXmlHelper.canBePromoted(PKG_N_MR1, UID_N_MR1)).isTrue();
+ }
assertEquals(channel1,
mXmlHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1.getId(), false));
compareChannels(channel2,
@@ -6293,14 +6310,21 @@ public class PreferencesHelperTest extends UiServiceTestCase {
}, 20, 50);
}
- private static NotificationChannel cloneChannel(NotificationChannel original) {
- Parcel parcel = Parcel.obtain();
- try {
- original.writeToParcel(parcel, 0);
- parcel.setDataPosition(0);
- return NotificationChannel.CREATOR.createFromParcel(parcel);
- } finally {
- parcel.recycle();
- }
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void testNoAppHasPermissionToPromoteByDefault() {
+ mHelper.setShowBadge(PKG_P, UID_P, true);
+ assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isFalse();
+ }
+
+ @Test
+ @EnableFlags(android.app.Flags.FLAG_UI_RICH_ONGOING)
+ public void testSetCanBePromoted() {
+ mHelper.setCanBePromoted(PKG_P, UID_P, true);
+ assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isTrue();
+
+ mHelper.setCanBePromoted(PKG_P, UID_P, false);
+ assertThat(mHelper.canBePromoted(PKG_P, UID_P)).isFalse();
+ verify(mHandler, never()).requestSort();
}
}
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 12069284e462..d4cba8d726fb 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -18,7 +18,7 @@ package com.android.server.notification;
import static android.app.AutomaticZenRule.TYPE_BEDTIME;
import static android.app.AutomaticZenRule.TYPE_IMMERSIVE;
-import static android.app.AutomaticZenRule.TYPE_SCHEDULE_CALENDAR;
+import static android.app.AutomaticZenRule.TYPE_SCHEDULE_TIME;
import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
import static android.app.Flags.FLAG_MODES_API;
import static android.app.Flags.FLAG_MODES_UI;
@@ -221,7 +221,7 @@ import platform.test.runner.parameterized.Parameters;
@TestableLooper.RunWithLooper
public class ZenModeHelperTest extends UiServiceTestCase {
- private static final String EVENTS_DEFAULT_RULE_ID = ZenModeConfig.EVENTS_DEFAULT_RULE_ID;
+ private static final String EVENTS_DEFAULT_RULE_ID = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID;
private static final String SCHEDULE_DEFAULT_RULE_ID =
ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID;
private static final String CUSTOM_PKG_NAME = "not.android";
@@ -1216,7 +1216,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// list for tracking which ids we've seen in the pulled atom output
List<String> ids = new ArrayList<>();
- ids.addAll(ZenModeConfig.DEFAULT_RULE_IDS);
+ ids.addAll(ZenModeConfig.getDefaultRuleIds());
ids.add(""); // empty string for root config
for (StatsEvent ev : events) {
@@ -1793,14 +1793,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// check default rules
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
assertTrue(rules.size() != 0);
- for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
+ for (String defaultId : ZenModeConfig.getDefaultRuleIds()) {
assertTrue(rules.containsKey(defaultId));
}
assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
}
-
@Test
public void testReadXmlAllDisabledRulesResetDefaultRules() throws Exception {
setupZenConfig();
@@ -1830,7 +1829,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// check default rules
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
assertTrue(rules.size() != 0);
- for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
+ for (String defaultId : ZenModeConfig.getDefaultRuleIds()) {
assertTrue(rules.containsKey(defaultId));
}
assertFalse(rules.containsKey("customRule"));
@@ -1839,6 +1838,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
}
@Test
+ @DisableFlags(FLAG_MODES_UI) // modes_ui has only 1 default rule
public void testReadXmlOnlyOneDefaultRuleExists() throws Exception {
setupZenConfig();
Policy originalPolicy = mZenModeHelper.getNotificationPolicy();
@@ -1882,11 +1882,11 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// check default rules
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
- assertTrue(rules.size() != 0);
- for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
- assertTrue(rules.containsKey(defaultId));
+ assertThat(rules).isNotEmpty();
+ for (String defaultId : ZenModeConfig.getDefaultRuleIds()) {
+ assertThat(rules).containsKey(defaultId);
}
- assertFalse(rules.containsKey("customRule"));
+ assertThat(rules).doesNotContainKey("customRule");
assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
}
@@ -1932,13 +1932,13 @@ public class ZenModeHelperTest extends UiServiceTestCase {
defaultEventRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
defaultEventRule.conditionId = ZenModeConfig.toScheduleConditionId(
defaultEventRuleInfo);
- defaultEventRule.id = ZenModeConfig.EVENTS_DEFAULT_RULE_ID;
+ defaultEventRule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID;
defaultScheduleRule.zenPolicy = new ZenPolicy.Builder()
.allowAlarms(false)
.allowMedia(false)
.allowRepeatCallers(false)
.build();
- automaticRules.put(ZenModeConfig.EVENTS_DEFAULT_RULE_ID, defaultEventRule);
+ automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, defaultEventRule);
mZenModeHelper.mConfig.automaticRules = automaticRules;
@@ -1951,18 +1951,19 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
// check default rules
+ int expectedNumAutoRules = 1 + ZenModeConfig.getDefaultRuleIds().size(); // custom + default
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
- assertEquals(3, rules.size());
- for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
- assertTrue(rules.containsKey(defaultId));
+ assertThat(rules).hasSize(expectedNumAutoRules);
+ for (String defaultId : ZenModeConfig.getDefaultRuleIds()) {
+ assertThat(rules).containsKey(defaultId);
}
- assertTrue(rules.containsKey("customRule"));
+ assertThat(rules).containsKey("customRule");
assertEquals(originalPolicy, mZenModeHelper.getNotificationPolicy());
List<StatsEvent> events = new LinkedList<>();
mZenModeHelper.pullRules(events);
- assertEquals(4, events.size());
+ assertThat(events).hasSize(expectedNumAutoRules + 1); // auto + manual
}
@Test
@@ -2151,8 +2152,8 @@ public class ZenModeHelperTest extends UiServiceTestCase {
defaultEventRule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
defaultEventRule.conditionId = ZenModeConfig.toScheduleConditionId(
defaultEventRuleInfo);
- defaultEventRule.id = ZenModeConfig.EVENTS_DEFAULT_RULE_ID;
- automaticRules.put(ZenModeConfig.EVENTS_DEFAULT_RULE_ID, defaultEventRule);
+ defaultEventRule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID;
+ automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, defaultEventRule);
mZenModeHelper.mConfig.automaticRules = automaticRules;
@@ -2167,7 +2168,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// check default rules
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
assertThat(rules.size()).isGreaterThan(0);
- for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
+ for (String defaultId : ZenModeConfig.getDefaultRuleIds()) {
assertThat(rules).containsKey(defaultId);
ZenRule rule = rules.get(defaultId);
assertThat(rule.zenPolicy).isNotNull();
@@ -2371,7 +2372,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
// Find default rules; check they have non-null policies; check that they match the default
// and not whatever has been set up in setupZenConfig.
ArrayMap<String, ZenModeConfig.ZenRule> rules = mZenModeHelper.mConfig.automaticRules;
- for (String defaultId : ZenModeConfig.DEFAULT_RULE_IDS) {
+ for (String defaultId : ZenModeConfig.getDefaultRuleIds()) {
assertThat(rules).containsKey(defaultId);
ZenRule rule = rules.get(defaultId);
assertThat(rule.zenPolicy).isNotNull();
@@ -6884,7 +6885,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.onUserSwitched(101);
ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get(
- ZenModeConfig.EVENTS_DEFAULT_RULE_ID);
+ ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
assertThat(eventsRule).isNotNull();
assertThat(eventsRule.zenPolicy).isNull();
@@ -6900,7 +6901,7 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.onUserSwitched(201);
ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get(
- ZenModeConfig.EVENTS_DEFAULT_RULE_ID);
+ ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
assertThat(eventsRule).isNotNull();
assertThat(eventsRule.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy());
@@ -6915,11 +6916,11 @@ public class ZenModeHelperTest extends UiServiceTestCase {
mZenModeHelper.onUserSwitched(301);
ZenRule eventsRule = mZenModeHelper.mConfig.automaticRules.get(
- ZenModeConfig.EVENTS_DEFAULT_RULE_ID);
+ ZenModeConfig.EVERY_NIGHT_DEFAULT_RULE_ID);
assertThat(eventsRule).isNotNull();
assertThat(eventsRule.zenPolicy).isEqualTo(mZenModeHelper.getDefaultZenPolicy());
- assertThat(eventsRule.type).isEqualTo(TYPE_SCHEDULE_CALENDAR);
+ assertThat(eventsRule.type).isEqualTo(TYPE_SCHEDULE_TIME);
assertThat(eventsRule.triggerDescription).isNotEmpty();
}
@@ -7008,6 +7009,46 @@ public class ZenModeHelperTest extends UiServiceTestCase {
assertThat(zenRule.zenPolicy).isNotSameInstanceAs(mZenModeHelper.getDefaultZenPolicy());
}
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void readXml_withDisabledEventsRule_deletesIt() throws Exception {
+ ZenRule rule = new ZenRule();
+ rule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID;
+ rule.name = "Events";
+ rule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ rule.conditionId = Uri.parse("events");
+
+ rule.enabled = false;
+ mZenModeHelper.mConfig.automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, rule);
+ ByteArrayOutputStream xmlBytes = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_UI);
+ TypedXmlPullParser parser = getParserForByteStream(xmlBytes);
+
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules).doesNotContainKey(
+ ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
+ }
+
+ @Test
+ @EnableFlags({FLAG_MODES_API, FLAG_MODES_UI})
+ public void readXml_withEnabledEventsRule_keepsIt() throws Exception {
+ ZenRule rule = new ZenRule();
+ rule.id = ZenModeConfig.EVENTS_OBSOLETE_RULE_ID;
+ rule.name = "Events";
+ rule.zenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+ rule.conditionId = Uri.parse("events");
+
+ rule.enabled = true;
+ mZenModeHelper.mConfig.automaticRules.put(ZenModeConfig.EVENTS_OBSOLETE_RULE_ID, rule);
+ ByteArrayOutputStream xmlBytes = writeXmlAndPurge(ZenModeConfig.XML_VERSION_MODES_UI);
+ TypedXmlPullParser parser = getParserForByteStream(xmlBytes);
+
+ mZenModeHelper.readXml(parser, false, UserHandle.USER_ALL);
+
+ assertThat(mZenModeHelper.mConfig.automaticRules).containsKey(
+ ZenModeConfig.EVENTS_OBSOLETE_RULE_ID);
+ }
+
private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode,
@Nullable ZenPolicy zenPolicy) {
ZenRule rule = new ZenRule();
diff --git a/telephony/common/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java
index a6781478a765..0f4809c2918d 100644
--- a/telephony/common/android/telephony/LocationAccessPolicy.java
+++ b/telephony/common/android/telephony/LocationAccessPolicy.java
@@ -33,6 +33,7 @@ import android.util.Log;
import android.widget.Toast;
import com.android.internal.telephony.TelephonyPermissions;
+import com.android.internal.telephony.flags.Flags;
import com.android.internal.telephony.util.TelephonyUtils;
/**
@@ -283,6 +284,8 @@ public final class LocationAccessPolicy {
int minSdkVersion = Manifest.permission.ACCESS_FINE_LOCATION.equals(permissionToCheck)
? query.minSdkVersionForFine : query.minSdkVersionForCoarse;
+ UserHandle callingUserHandle = UserHandle.getUserHandleForUid(query.callingUid);
+
// If the app fails for some reason, see if it should be allowed to proceed.
if (minSdkVersion > MAX_SDK_FOR_ANY_ENFORCEMENT) {
String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog
@@ -291,7 +294,8 @@ public final class LocationAccessPolicy {
+ query.method;
logError(context, query, errorMsg);
return null;
- } else if (!isAppAtLeastSdkVersion(context, query.callingPackage, minSdkVersion)) {
+ } else if (!isAppAtLeastSdkVersion(context, callingUserHandle, query.callingPackage,
+ minSdkVersion)) {
String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog
+ " because it doesn't target API " + minSdkVersion + " yet."
+ " Please fix this app. Called from " + query.method;
@@ -420,11 +424,19 @@ public final class LocationAccessPolicy {
}
}
- private static boolean isAppAtLeastSdkVersion(Context context, String pkgName, int sdkVersion) {
+ private static boolean isAppAtLeastSdkVersion(Context context,
+ @NonNull UserHandle callingUserHandle, String pkgName, int sdkVersion) {
try {
- if (context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion
- >= sdkVersion) {
- return true;
+ if (Flags.hsumPackageManager()) {
+ if (context.getPackageManager().getApplicationInfoAsUser(
+ pkgName, 0, callingUserHandle).targetSdkVersion >= sdkVersion) {
+ return true;
+ }
+ } else {
+ if (context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion
+ >= sdkVersion) {
+ return true;
+ }
}
} catch (PackageManager.NameNotFoundException e) {
// In case of exception, assume known app (more strict checking)
diff --git a/tests/FlickerTests/ActivityEmbedding/OWNERS b/tests/FlickerTests/ActivityEmbedding/OWNERS
new file mode 100644
index 000000000000..981b3168cdbb
--- /dev/null
+++ b/tests/FlickerTests/ActivityEmbedding/OWNERS
@@ -0,0 +1 @@
+# Bug component: 1168918
diff --git a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
index 945907009a9c..b5258dfc9c3c 100644
--- a/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
+++ b/tests/Input/src/com/android/server/input/debug/TouchpadDebugViewTest.java
@@ -57,6 +57,8 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.function.Consumer;
+
/**
* Build/Install/Run:
* atest TouchpadDebugViewTest
@@ -99,10 +101,12 @@ public class TouchpadDebugViewTest {
when(mInputManager.getInputDevice(TOUCHPAD_DEVICE_ID)).thenReturn(inputDevice);
+ Consumer<Integer> touchpadSwitchHandler = id -> {};
+
mTouchpadDebugView = new TouchpadDebugView(mTestableContext, TOUCHPAD_DEVICE_ID,
new TouchpadHardwareProperties.Builder(0f, 0f, 500f,
500f, 45f, 47f, -4f, 5f, (short) 10, true,
- true).build());
+ true).build(), touchpadSwitchHandler);
mTouchpadDebugView.measure(
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
@@ -321,26 +325,30 @@ public class TouchpadDebugViewTest {
new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0,
new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);
- assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(118, 151, 99));
+ assertEquals(((ColorDrawable) child.getBackground()).getColor(),
+ Color.parseColor("#769763"));
mTouchpadDebugView.updateHardwareState(
new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);
- assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(84, 85, 169));
+ assertEquals(((ColorDrawable) child.getBackground()).getColor(),
+ Color.parseColor("#5455A9"));
mTouchpadDebugView.updateHardwareState(
new TouchpadHardwareState(0, 1 /* buttonsDown */, 0, 0,
new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID);
- assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(118, 151, 99));
+ assertEquals(((ColorDrawable) child.getBackground()).getColor(),
+ Color.parseColor("#769763"));
// Color should not change because hardware state of a different touchpad
mTouchpadDebugView.updateHardwareState(
new TouchpadHardwareState(0, 0 /* buttonsDown */, 0, 0,
new TouchpadFingerState[0]), TOUCHPAD_DEVICE_ID + 1);
- assertEquals(((ColorDrawable) child.getBackground()).getColor(), Color.rgb(118, 151, 99));
+ assertEquals(((ColorDrawable) child.getBackground()).getColor(),
+ Color.parseColor("#769763"));
}
@Test
diff --git a/tools/systemfeatures/Android.bp b/tools/systemfeatures/Android.bp
index a9e63289ee93..590f7190881a 100644
--- a/tools/systemfeatures/Android.bp
+++ b/tools/systemfeatures/Android.bp
@@ -30,8 +30,8 @@ genrule {
name: "systemfeatures-gen-tests-srcs",
cmd: "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwNoFeatures --readonly=false > $(location RwNoFeatures.java) && " +
"$(location systemfeatures-gen-tool) com.android.systemfeatures.RoNoFeatures --readonly=true --feature-apis=WATCH > $(location RoNoFeatures.java) && " +
- "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwFeatures --readonly=false --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:-1 --feature=AUTO: > $(location RwFeatures.java) && " +
- "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoFeatures --readonly=true --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:-1 --feature=AUTO: --feature-apis=WATCH,PC > $(location RoFeatures.java)",
+ "$(location systemfeatures-gen-tool) com.android.systemfeatures.RwFeatures --readonly=false --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:UNAVAILABLE --feature=AUTO: > $(location RwFeatures.java) && " +
+ "$(location systemfeatures-gen-tool) com.android.systemfeatures.RoFeatures --readonly=true --feature=WATCH:1 --feature=WIFI:0 --feature=VULKAN:UNAVAILABLE --feature=AUTO: --feature-apis=WATCH,PC > $(location RoFeatures.java)",
out: [
"RwNoFeatures.java",
"RoNoFeatures.java",
diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
index 5df453deaf2a..dc0d8a39dbc1 100644
--- a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
+++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt
@@ -31,7 +31,7 @@ import javax.lang.model.element.Modifier
*
* <pre>
* <cmd> com.foo.RoSystemFeatures --readonly=true \
- * --feature=WATCH:0 --feature=AUTOMOTIVE: --feature=VULKAN:9348
+ * --feature=WATCH:0 --feature=AUTOMOTIVE: --feature=VULKAN:9348 --feature=PC:UNAVAILABLE
* --feature-apis=WATCH,PC,LEANBACK
* </pre>
*
@@ -43,10 +43,10 @@ import javax.lang.model.element.Modifier
* @AssumeTrueForR8
* public static boolean hasFeatureWatch(Context context);
* @AssumeFalseForR8
- * public static boolean hasFeatureAutomotive(Context context);
+ * public static boolean hasFeaturePc(Context context);
* @AssumeTrueForR8
* public static boolean hasFeatureVulkan(Context context);
- * public static boolean hasFeaturePc(Context context);
+ * public static boolean hasFeatureAutomotive(Context context);
* public static boolean hasFeatureLeanback(Context context);
* public static Boolean maybeHasFeature(String feature, int version);
* }
@@ -67,7 +67,10 @@ object SystemFeaturesGenerator {
println("Usage: SystemFeaturesGenerator <outputClassName> [options]")
println(" Options:")
println(" --readonly=true|false Whether to encode features as build-time constants")
- println(" --feature=\$NAME:\$VER A feature+version pair (blank version == disabled)")
+ println(" --feature=\$NAME:\$VER A feature+version pair, where \$VER can be:")
+ println(" * blank/empty == undefined (variable API)")
+ println(" * valid int == enabled (constant API)")
+ println(" * UNAVAILABLE == disabled (constant API)")
println(" This will always generate associated query APIs,")
println(" adding to or replacing those from `--feature-apis=`.")
println(" --feature-apis=\$NAME_1,\$NAME_2")
@@ -89,7 +92,7 @@ object SystemFeaturesGenerator {
var readonly = false
var outputClassName: ClassName? = null
- val featureArgs = mutableListOf<FeatureArg>()
+ val featureArgs = mutableListOf<FeatureInfo>()
// We could just as easily hardcode this list, as the static API surface should change
// somewhat infrequently, but this decouples the codegen from the framework completely.
val featureApiArgs = mutableSetOf<String>()
@@ -122,7 +125,7 @@ object SystemFeaturesGenerator {
featureArgs.associateByTo(
features,
{ it.name },
- { FeatureInfo(it.name, it.version, readonly) },
+ { FeatureInfo(it.name, it.version, it.readonly && readonly) },
)
outputClassName
@@ -154,13 +157,17 @@ object SystemFeaturesGenerator {
* Parses a feature argument of the form "--feature=$NAME:$VER", where "$VER" is optional.
* * "--feature=WATCH:0" -> Feature enabled w/ version 0 (default version when enabled)
* * "--feature=WATCH:7" -> Feature enabled w/ version 7
- * * "--feature=WATCH:" -> Feature disabled
+ * * "--feature=WATCH:" -> Feature status undefined, runtime API generated
+ * * "--feature=WATCH:UNAVAILABLE" -> Feature disabled
*/
- private fun parseFeatureArg(arg: String): FeatureArg {
+ private fun parseFeatureArg(arg: String): FeatureInfo {
val featureArgs = arg.substring(FEATURE_ARG.length).split(":")
val name = parseFeatureName(featureArgs[0])
- val version = featureArgs.getOrNull(1)?.toIntOrNull()
- return FeatureArg(name, version)
+ return when (featureArgs.getOrNull(1)) {
+ null, "" -> FeatureInfo(name, null, readonly = false)
+ "UNAVAILABLE" -> FeatureInfo(name, null, readonly = true)
+ else -> FeatureInfo(name, featureArgs[1].toIntOrNull(), readonly = true)
+ }
}
private fun parseFeatureName(name: String): String =
@@ -267,7 +274,5 @@ object SystemFeaturesGenerator {
builder.addMethod(methodBuilder.build())
}
- private data class FeatureArg(val name: String, val version: Int?)
-
private data class FeatureInfo(val name: String, val version: Int?, val readonly: Boolean)
}
diff --git a/tools/systemfeatures/tests/golden/RoFeatures.java.gen b/tools/systemfeatures/tests/golden/RoFeatures.java.gen
index 724639b52d23..dfc2937dc41d 100644
--- a/tools/systemfeatures/tests/golden/RoFeatures.java.gen
+++ b/tools/systemfeatures/tests/golden/RoFeatures.java.gen
@@ -3,7 +3,7 @@
// --readonly=true \
// --feature=WATCH:1 \
// --feature=WIFI:0 \
-// --feature=VULKAN:-1 \
+// --feature=VULKAN:UNAVAILABLE \
// --feature=AUTO: \
// --feature-apis=WATCH,PC
package com.android.systemfeatures;
@@ -62,9 +62,8 @@ public final class RoFeatures {
*
* @hide
*/
- @AssumeFalseForR8
public static boolean hasFeatureAuto(Context context) {
- return false;
+ return hasFeatureFallback(context, PackageManager.FEATURE_AUTO);
}
private static boolean hasFeatureFallback(Context context, String featureName) {
@@ -79,8 +78,7 @@ public final class RoFeatures {
switch (featureName) {
case PackageManager.FEATURE_WATCH: return 1 >= version;
case PackageManager.FEATURE_WIFI: return 0 >= version;
- case PackageManager.FEATURE_VULKAN: return -1 >= version;
- case PackageManager.FEATURE_AUTO: return false;
+ case PackageManager.FEATURE_VULKAN: return false;
default: break;
}
return null;
diff --git a/tools/systemfeatures/tests/golden/RwFeatures.java.gen b/tools/systemfeatures/tests/golden/RwFeatures.java.gen
index 6f897591e48f..89097fbfd180 100644
--- a/tools/systemfeatures/tests/golden/RwFeatures.java.gen
+++ b/tools/systemfeatures/tests/golden/RwFeatures.java.gen
@@ -3,7 +3,7 @@
// --readonly=false \
// --feature=WATCH:1 \
// --feature=WIFI:0 \
-// --feature=VULKAN:-1 \
+// --feature=VULKAN:UNAVAILABLE \
// --feature=AUTO:
package com.android.systemfeatures;
diff --git a/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java
index 6dfd244a807b..c3a23cbd8e48 100644
--- a/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java
+++ b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java
@@ -110,10 +110,13 @@ public class SystemFeaturesGeneratorTest {
assertThat(RoFeatures.hasFeatureWatch(mContext)).isTrue();
assertThat(RoFeatures.hasFeatureWifi(mContext)).isTrue();
assertThat(RoFeatures.hasFeatureVulkan(mContext)).isFalse();
- assertThat(RoFeatures.hasFeatureAuto(mContext)).isFalse();
verify(mPackageManager, never()).hasSystemFeature(anyString(), anyInt());
- // For defined feature types, conditional queries should reflect the build-time versions.
+ // For defined feature types, conditional queries should reflect either:
+ // * Enabled if the feature version is specified
+ // * Disabled if UNAVAILABLE is specified
+ // * Unknown if no version value is provided
+
// VERSION=1
assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, -1)).isTrue();
assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WATCH, 0)).isTrue();
@@ -124,15 +127,19 @@ public class SystemFeaturesGeneratorTest {
assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, 0)).isTrue();
assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_WIFI, 100)).isFalse();
- // VERSION=-1
- assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, -1)).isTrue();
+ // VERSION=UNAVAILABLE
+ assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, -1)).isFalse();
assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isFalse();
assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 100)).isFalse();
- // DISABLED
- assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, -1)).isFalse();
- assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isFalse();
- assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 100)).isFalse();
+ // VERSION=
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTO, 0)).thenReturn(false);
+ assertThat(RoFeatures.hasFeatureAuto(mContext)).isFalse();
+ when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTO, 0)).thenReturn(true);
+ assertThat(RoFeatures.hasFeatureAuto(mContext)).isTrue();
+ assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, -1)).isNull();
+ assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull();
+ assertThat(RoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 100)).isNull();
// For feature APIs without an associated feature definition, conditional queries should
// report null, and explicit queries should report runtime-defined versions.
@@ -148,5 +155,6 @@ public class SystemFeaturesGeneratorTest {
assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", -1)).isNull();
assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull();
assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", 100)).isNull();
+ assertThat(RoFeatures.maybeHasFeature("", 0)).isNull();
}
}