summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java4
-rw-r--r--cmds/app_process/Android.bp1
-rw-r--r--core/api/current.txt25
-rw-r--r--core/api/lint-baseline.txt11
-rw-r--r--core/api/system-current.txt2
-rw-r--r--core/java/android/app/FullscreenRequestHandler.java15
-rw-r--r--core/java/android/content/res/ApkAssets.java2
-rw-r--r--core/java/android/content/res/AssetManager.java11
-rw-r--r--core/java/android/content/res/Resources.java23
-rw-r--r--core/java/android/content/res/ResourcesImpl.java17
-rw-r--r--core/java/android/content/res/XmlBlock.java10
-rw-r--r--core/java/android/os/Parcel.java8
-rw-r--r--core/java/android/security/flags.aconfig7
-rw-r--r--core/java/android/util/TypedValue.java6
-rw-r--r--core/java/android/view/RoundScrollbarRenderer.java4
-rw-r--r--core/java/android/view/ScrollCaptureConnection.java44
-rw-r--r--core/java/android/view/SurfaceControl.java2
-rw-r--r--core/java/android/view/ViewRootImpl.java33
-rw-r--r--core/java/android/view/WindowManager.java77
-rw-r--r--core/java/android/view/XrWindowProperties.java30
-rw-r--r--core/java/android/window/DesktopModeFlags.java2
-rw-r--r--core/java/com/android/internal/policy/KeyInterceptionInfo.java4
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java35
-rw-r--r--core/jni/Android.bp1
-rw-r--r--core/jni/android_util_AssetManager.cpp4
-rw-r--r--core/proto/android/server/windowmanagerservice.proto2
-rw-r--r--core/res/Android.bp1
-rw-r--r--core/res/AndroidManifest.xml7
-rw-r--r--core/res/res/xml/sms_short_codes.xml2
-rw-r--r--core/tests/coretests/res/xml/flags.xml4
-rw-r--r--core/tests/coretests/src/android/content/res/XmlResourcesFlaggedTest.kt114
-rw-r--r--core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java70
-rw-r--r--data/etc/platform.xml6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/AppHandleAndHeaderVisibilityHelper.kt15
-rw-r--r--libs/androidfw/AssetManager2.cpp16
-rw-r--r--libs/androidfw/include/androidfw/AssetManager2.h11
-rw-r--r--libs/androidfw/tests/AssetManager2_test.cpp15
-rw-r--r--libs/androidfw/tests/data/flagged/AndroidManifest.xml20
-rw-r--r--libs/androidfw/tests/data/flagged/R.h35
-rwxr-xr-xlibs/androidfw/tests/data/flagged/build28
-rw-r--r--libs/androidfw/tests/data/flagged/flagged.apkbin0 -> 1837 bytes
-rw-r--r--libs/androidfw/tests/data/flagged/res/xml/flagged.xml18
-rw-r--r--libs/hwui/Android.bp8
-rw-r--r--libs/hwui/jni/Bitmap.cpp14
-rw-r--r--media/java/android/media/projection/MediaProjectionAppContent.aidl19
-rw-r--r--media/java/android/media/projection/MediaProjectionAppContent.java123
-rw-r--r--media/java/android/media/projection/MediaProjectionConfig.java354
-rw-r--r--media/java/android/media/projection/MediaProjectionManager.java10
-rw-r--r--media/java/android/media/projection/TEST_MAPPING2
-rw-r--r--media/java/android/media/quality/PictureProfile.java12
-rw-r--r--media/java/android/media/quality/aidl/android/media/quality/IMediaQualityManager.aidl2
-rw-r--r--media/tests/projection/Android.bp1
-rw-r--r--media/tests/projection/src/android/media/projection/MediaProjectionAppContentTest.java86
-rw-r--r--media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java32
-rw-r--r--packages/SettingsLib/SettingsSpinner/res/drawable-v36/settings_expressive_spinner_dropdown_background.xml21
-rw-r--r--packages/SettingsLib/SettingsSpinner/res/drawable-v36/settings_expressive_spinner_dropdown_item_selected.xml28
-rw-r--r--packages/SettingsLib/SettingsSpinner/res/layout-v33/settings_spinner_view.xml2
-rw-r--r--packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_preference_full.xml27
-rw-r--r--packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_preference_full_outlined.xml33
-rw-r--r--packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_preference_outlined.xml33
-rw-r--r--packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_view_full.xml22
-rw-r--r--packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_view_full_outlined.xml26
-rw-r--r--packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_view_large.xml22
-rw-r--r--packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_view_large_outlined.xml26
-rw-r--r--packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressvie_spinner_dropdown_view.xml36
-rw-r--r--packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressvie_spinner_view.xml22
-rw-r--r--packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressvie_spinner_view_outlined.xml26
-rw-r--r--packages/SettingsLib/SettingsSpinner/res/values-v33/styles.xml2
-rw-r--r--packages/SettingsLib/SettingsSpinner/res/values-v36/attr.xml25
-rw-r--r--packages/SettingsLib/SettingsSpinner/res/values-v36/styles.xml59
-rw-r--r--packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerAdapter.java92
-rw-r--r--packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java92
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_expressive_spinner_background.xml53
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_expressive_spinner_background_outlined.xml55
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_expressive_spinner_dropdown_background.xml38
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v36/styles_expressive.xml13
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v36/themes_expressive.xml8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt3
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java48
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig10
-rw-r--r--packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/ContentOverscrollEffect.kt7
-rw-r--r--packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/effect/OffsetOverscrollEffectTest.kt181
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt4
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt151
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt27
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt6
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt5
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt10
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/communal/util/UserTouchActivityNotifierTest.kt74
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt64
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt39
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt37
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt35
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java15
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationsInteractorTest.kt19
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/communal/util/UserTouchActivityNotifier.kt58
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt70
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt244
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java34
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionUtils.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt123
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt123
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowImageInflater.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/AvalancheReplaceHunWhenCritical.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt)26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java56
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/OutputMediaItemListProxyTest.java67
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/UserTouchActivityNotifierKosmos.kt28
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt4
-rw-r--r--services/art-profile-extra8
-rw-r--r--services/core/java/com/android/server/GestureLauncherService.java43
-rw-r--r--services/core/java/com/android/server/audio/HardeningEnforcer.java37
-rw-r--r--services/core/java/com/android/server/connectivity/PacProxyService.java5
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java6
-rw-r--r--services/core/java/com/android/server/input/NativeInputManagerService.java6
-rw-r--r--services/core/java/com/android/server/input/SysfsNodeMonitor.java203
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java39
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityService.java545
-rw-r--r--services/core/java/com/android/server/media/quality/MediaQualityUtils.java8
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java16
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java217
-rw-r--r--services/core/java/com/android/server/security/AttestationVerificationManagerService.java5
-rw-r--r--services/core/java/com/android/server/tv/TvInputHal.java2
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java12
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java21
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java9
-rw-r--r--services/core/jni/Android.bp2
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp7
-rw-r--r--services/core/jni/com_android_server_tv_TvInputHal.cpp7
-rw-r--r--services/core/jni/tvinput/JTvInputHal.cpp19
-rw-r--r--services/core/jni/tvinput/JTvInputHal.h2
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt7
-rw-r--r--services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java40
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java143
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java30
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java49
-rw-r--r--telecomm/java/android/telecom/Connection.java10
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java29
-rw-r--r--tests/AppJankTest/src/android/app/jank/tests/JankUtils.java23
-rw-r--r--tests/Input/src/com/android/server/input/InputManagerServiceTests.kt22
166 files changed, 3826 insertions, 1871 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 5dfb3754e8fb..7e421676b3c9 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -2039,8 +2039,8 @@ class JobConcurrencyManager {
DeviceConfig.Properties properties =
DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
- // Concurrency limit should be in the range [8, MAX_CONCURRENCY_LIMIT].
- mSteadyStateConcurrencyLimit = Math.max(8, Math.min(MAX_CONCURRENCY_LIMIT,
+ // Concurrency limit should be in the range [1, MAX_CONCURRENCY_LIMIT].
+ mSteadyStateConcurrencyLimit = Math.max(1, Math.min(MAX_CONCURRENCY_LIMIT,
properties.getInt(KEY_CONCURRENCY_LIMIT, DEFAULT_CONCURRENCY_LIMIT)));
mScreenOffAdjustmentDelayMs = properties.getLong(
diff --git a/cmds/app_process/Android.bp b/cmds/app_process/Android.bp
index 3c7609e1d8ed..a1575173ded6 100644
--- a/cmds/app_process/Android.bp
+++ b/cmds/app_process/Android.bp
@@ -56,7 +56,6 @@ cc_binary {
"libsigchain",
"libutils",
- "libutilscallstack",
// This is a list of libraries that need to be included in order to avoid
// bad apps. This prevents a library from having a mismatch when resolving
diff --git a/core/api/current.txt b/core/api/current.txt
index 151a6738505c..27aa3518f958 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -273,6 +273,7 @@ package android {
field public static final String READ_SYNC_SETTINGS = "android.permission.READ_SYNC_SETTINGS";
field public static final String READ_SYNC_STATS = "android.permission.READ_SYNC_STATS";
field @FlaggedApi("com.android.settingslib.flags.settings_catalyst") public static final String READ_SYSTEM_PREFERENCES = "android.permission.READ_SYSTEM_PREFERENCES";
+ field @FlaggedApi("com.android.update_engine.minor_changes_2025q4") public static final String READ_UPDATE_ENGINE_LOGS = "android.permission.READ_UPDATE_ENGINE_LOGS";
field public static final String READ_VOICEMAIL = "com.android.voicemail.permission.READ_VOICEMAIL";
field public static final String REBOOT = "android.permission.REBOOT";
field public static final String RECEIVE_BOOT_COMPLETED = "android.permission.RECEIVE_BOOT_COMPLETED";
@@ -27222,12 +27223,34 @@ package android.media.projection {
method public void onStop();
}
+ @FlaggedApi("com.android.media.projection.flags.app_content_sharing") public final class MediaProjectionAppContent implements android.os.Parcelable {
+ ctor public MediaProjectionAppContent(@NonNull android.graphics.Bitmap, @NonNull CharSequence, int);
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.projection.MediaProjectionAppContent> CREATOR;
+ }
+
public final class MediaProjectionConfig implements android.os.Parcelable {
method @NonNull public static android.media.projection.MediaProjectionConfig createConfigForDefaultDisplay();
method @NonNull public static android.media.projection.MediaProjectionConfig createConfigForUserChoice();
method public int describeContents();
+ method @FlaggedApi("com.android.media.projection.flags.app_content_sharing") public int getInitiallySelectedSource();
+ method @FlaggedApi("com.android.media.projection.flags.app_content_sharing") public int getProjectionSources();
+ method @FlaggedApi("com.android.media.projection.flags.app_content_sharing") @Nullable public CharSequence getRequesterHint();
+ method @FlaggedApi("com.android.media.projection.flags.app_content_sharing") public boolean isSourceEnabled(int);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.projection.MediaProjectionConfig> CREATOR;
+ field @FlaggedApi("com.android.media.projection.flags.app_content_sharing") public static final int PROJECTION_SOURCE_APP = 8; // 0x8
+ field @FlaggedApi("com.android.media.projection.flags.app_content_sharing") public static final int PROJECTION_SOURCE_APP_CONTENT = 16; // 0x10
+ field @FlaggedApi("com.android.media.projection.flags.app_content_sharing") public static final int PROJECTION_SOURCE_DISPLAY = 2; // 0x2
+ }
+
+ @FlaggedApi("com.android.media.projection.flags.app_content_sharing") public static final class MediaProjectionConfig.Builder {
+ ctor public MediaProjectionConfig.Builder();
+ method @NonNull public android.media.projection.MediaProjectionConfig build();
+ method @NonNull public android.media.projection.MediaProjectionConfig.Builder setInitiallySelectedSource(int);
+ method @NonNull public android.media.projection.MediaProjectionConfig.Builder setRequesterHint(@Nullable String);
+ method @NonNull public android.media.projection.MediaProjectionConfig.Builder setSourceEnabled(int, boolean);
}
public final class MediaProjectionManager {
@@ -44050,6 +44073,7 @@ package android.telecom {
field public static final String EVENT_CALL_PULL_FAILED = "android.telecom.event.CALL_PULL_FAILED";
field public static final String EVENT_CALL_REMOTELY_HELD = "android.telecom.event.CALL_REMOTELY_HELD";
field public static final String EVENT_CALL_REMOTELY_UNHELD = "android.telecom.event.CALL_REMOTELY_UNHELD";
+ field @FlaggedApi("com.android.server.telecom.flags.call_sequencing_call_resume_failed") public static final String EVENT_CALL_RESUME_FAILED = "android.telecom.event.CALL_RESUME_FAILED";
field public static final String EVENT_CALL_SWITCH_FAILED = "android.telecom.event.CALL_SWITCH_FAILED";
field public static final String EVENT_MERGE_COMPLETE = "android.telecom.event.MERGE_COMPLETE";
field public static final String EVENT_MERGE_START = "android.telecom.event.MERGE_START";
@@ -56113,6 +56137,7 @@ package android.view {
@FlaggedApi("android.xr.xr_manifest_entries") public final class XrWindowProperties {
field @FlaggedApi("android.xr.xr_manifest_entries") public static final String PROPERTY_XR_ACTIVITY_START_MODE = "android.window.PROPERTY_XR_ACTIVITY_START_MODE";
field @FlaggedApi("android.xr.xr_manifest_entries") public static final String PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED = "android.window.PROPERTY_XR_BOUNDARY_TYPE_RECOMMENDED";
+ field @FlaggedApi("android.xr.xr_manifest_entries") public static final String PROPERTY_XR_USES_CUSTOM_FULL_SPACE_MANAGED_ANIMATION = "android.window.PROPERTY_XR_USES_CUSTOM_FULL_SPACE_MANAGED_ANIMATION";
field @FlaggedApi("android.xr.xr_manifest_entries") public static final String XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED = "XR_ACTIVITY_START_MODE_FULL_SPACE_MANAGED";
field @FlaggedApi("android.xr.xr_manifest_entries") public static final String XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED = "XR_ACTIVITY_START_MODE_FULL_SPACE_UNMANAGED";
field @FlaggedApi("android.xr.xr_manifest_entries") public static final String XR_ACTIVITY_START_MODE_HOME_SPACE = "XR_ACTIVITY_START_MODE_HOME_SPACE";
diff --git a/core/api/lint-baseline.txt b/core/api/lint-baseline.txt
index 577113b80d84..3895a512abc7 100644
--- a/core/api/lint-baseline.txt
+++ b/core/api/lint-baseline.txt
@@ -1,4 +1,3 @@
-
// Baseline format: 1.0
BroadcastBehavior: android.app.AlarmManager#ACTION_NEXT_ALARM_CLOCK_CHANGED:
Field 'ACTION_NEXT_ALARM_CLOCK_CHANGED' is missing @BroadcastBehavior
@@ -244,6 +243,8 @@ BroadcastBehavior: android.telephony.TelephonyManager#ACTION_SUBSCRIPTION_SPECIF
Field 'ACTION_SUBSCRIPTION_SPECIFIC_CARRIER_IDENTITY_CHANGED' is missing @BroadcastBehavior
BroadcastBehavior: android.telephony.euicc.EuiccManager#ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE:
Field 'ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE' is missing @BroadcastBehavior
+
+
DeprecationMismatch: android.accounts.AccountManager#newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, String[], boolean, String, String, String[], android.os.Bundle):
Method android.accounts.AccountManager.newChooseAccountIntent(android.accounts.Account, java.util.ArrayList<android.accounts.Account>, String[], boolean, String, String, String[], android.os.Bundle): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match
DeprecationMismatch: android.app.Activity#enterPictureInPictureMode():
@@ -380,6 +381,8 @@ DeprecationMismatch: android.webkit.WebViewDatabase#hasFormData():
Method android.webkit.WebViewDatabase.hasFormData(): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match
DeprecationMismatch: javax.microedition.khronos.egl.EGL10#eglCreatePixmapSurface(javax.microedition.khronos.egl.EGLDisplay, javax.microedition.khronos.egl.EGLConfig, Object, int[]):
Method javax.microedition.khronos.egl.EGL10.eglCreatePixmapSurface(javax.microedition.khronos.egl.EGLDisplay, javax.microedition.khronos.egl.EGLConfig, Object, int[]): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match
+
+
FlaggedApiLiteral: android.Manifest.permission#BIND_APP_FUNCTION_SERVICE:
@FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER).
FlaggedApiLiteral: android.Manifest.permission#BIND_TV_AD_SERVICE:
@@ -390,6 +393,8 @@ FlaggedApiLiteral: android.Manifest.permission#QUERY_ADVANCED_PROTECTION_MODE:
@FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.security.Flags.FLAG_AAPM_API).
FlaggedApiLiteral: android.Manifest.permission#RANGING:
@FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.permission.flags.Flags.FLAG_RANGING_PERMISSION_ENABLED).
+FlaggedApiLiteral: android.Manifest.permission#READ_UPDATE_ENGINE_LOGS:
+ @FlaggedApi contains a string literal, but should reference the field generated by aconfig (com.android.update_engine.Flags.FLAG_MINOR_CHANGES_2025Q4, however this flag doesn't seem to exist).
FlaggedApiLiteral: android.Manifest.permission#REQUEST_OBSERVE_DEVICE_UUID_PRESENCE:
@FlaggedApi contains a string literal, but should reference the field generated by aconfig (android.companion.Flags.FLAG_DEVICE_PRESENCE).
FlaggedApiLiteral: android.R.attr#adServiceTypes:
@@ -1110,10 +1115,14 @@ RequiresPermission: android.webkit.WebSettings#setBlockNetworkLoads(boolean):
Method 'setBlockNetworkLoads' documentation mentions permissions without declaring @RequiresPermission
RequiresPermission: android.webkit.WebSettings#setGeolocationEnabled(boolean):
Method 'setGeolocationEnabled' documentation mentions permissions without declaring @RequiresPermission
+
+
Todo: android.hardware.camera2.params.StreamConfigurationMap:
Documentation mentions 'TODO'
Todo: android.provider.ContactsContract.RawContacts#newEntityIterator(android.database.Cursor):
Documentation mentions 'TODO'
+
+
UnflaggedApi: android.R.color#on_surface_disabled_material:
New API must be flagged with @FlaggedApi: field android.R.color.on_surface_disabled_material
UnflaggedApi: android.R.color#outline_disabled_material:
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 42c60b0ba0da..b92df4cf7884 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -18977,10 +18977,8 @@ package android.view {
public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable {
method public final long getUserActivityTimeout();
- method @FlaggedApi("com.android.hardware.input.override_power_key_behavior_in_focused_window") @RequiresPermission(android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW) public boolean isReceivePowerKeyDoublePressEnabled();
method public boolean isSystemApplicationOverlay();
method @FlaggedApi("android.companion.virtualdevice.flags.status_bar_and_insets") public void setInsetsParams(@NonNull java.util.List<android.view.WindowManager.InsetsParams>);
- method @FlaggedApi("com.android.hardware.input.override_power_key_behavior_in_focused_window") @RequiresPermission(android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW) public void setReceivePowerKeyDoublePressEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY) public void setSystemApplicationOverlay(boolean);
method public final void setUserActivityTimeout(long);
field @RequiresPermission(android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS) public static final int SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS = 524288; // 0x80000
diff --git a/core/java/android/app/FullscreenRequestHandler.java b/core/java/android/app/FullscreenRequestHandler.java
index c78c66aa62c0..5529349dea70 100644
--- a/core/java/android/app/FullscreenRequestHandler.java
+++ b/core/java/android/app/FullscreenRequestHandler.java
@@ -18,6 +18,7 @@ package android.app;
import static android.app.Activity.FULLSCREEN_MODE_REQUEST_EXIT;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -27,6 +28,7 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.IRemoteCallback;
import android.os.OutcomeReceiver;
+import android.window.DesktopModeFlags;
/**
* @hide
@@ -35,13 +37,15 @@ public class FullscreenRequestHandler {
@IntDef(prefix = { "RESULT_" }, value = {
RESULT_APPROVED,
RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY,
- RESULT_FAILED_NOT_TOP_FOCUSED
+ RESULT_FAILED_NOT_TOP_FOCUSED,
+ RESULT_FAILED_ALREADY_FULLY_EXPANDED
})
public @interface RequestResult {}
public static final int RESULT_APPROVED = 0;
public static final int RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY = 1;
public static final int RESULT_FAILED_NOT_TOP_FOCUSED = 2;
+ public static final int RESULT_FAILED_ALREADY_FULLY_EXPANDED = 3;
public static final String REMOTE_CALLBACK_RESULT_KEY = "result";
@@ -87,6 +91,9 @@ public class FullscreenRequestHandler {
case RESULT_FAILED_NOT_TOP_FOCUSED:
e = new IllegalStateException("The window is not the top focused window.");
break;
+ case RESULT_FAILED_ALREADY_FULLY_EXPANDED:
+ e = new IllegalStateException("The window is already fully expanded.");
+ break;
default:
callback.onResult(null);
break;
@@ -101,6 +108,12 @@ public class FullscreenRequestHandler {
if (windowingMode != WINDOWING_MODE_FULLSCREEN) {
return RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
}
+ return RESULT_APPROVED;
+ }
+ if (DesktopModeFlags.ENABLE_REQUEST_FULLSCREEN_BUGFIX.isTrue()
+ && (windowingMode == WINDOWING_MODE_FULLSCREEN
+ || windowingMode == WINDOWING_MODE_MULTI_WINDOW)) {
+ return RESULT_FAILED_ALREADY_FULLY_EXPANDED;
}
return RESULT_APPROVED;
}
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index f538e9ffffdd..3987f3abff0b 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -408,7 +408,7 @@ public final class ApkAssets {
Objects.requireNonNull(fileName, "fileName");
synchronized (this) {
long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName);
- try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) {
+ try (XmlBlock block = new XmlBlock(null, nativeXmlPtr, true)) {
XmlResourceParser parser = block.newParser();
// If nativeOpenXml doesn't throw, it will always return a valid native pointer,
// which makes newParser always return non-null. But let's be careful.
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index bcb50881d327..008bf2f522c3 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -1190,7 +1190,7 @@ public final class AssetManager implements AutoCloseable {
*/
public @NonNull XmlResourceParser openXmlResourceParser(int cookie, @NonNull String fileName)
throws IOException {
- try (XmlBlock block = openXmlBlockAsset(cookie, fileName)) {
+ try (XmlBlock block = openXmlBlockAsset(cookie, fileName, true)) {
XmlResourceParser parser = block.newParser(ID_NULL, new Validator());
// If openXmlBlockAsset doesn't throw, it will always return an XmlBlock object with
// a valid native pointer, which makes newParser always return non-null. But let's
@@ -1209,7 +1209,7 @@ public final class AssetManager implements AutoCloseable {
* @hide
*/
@NonNull XmlBlock openXmlBlockAsset(@NonNull String fileName) throws IOException {
- return openXmlBlockAsset(0, fileName);
+ return openXmlBlockAsset(0, fileName, true);
}
/**
@@ -1218,9 +1218,11 @@ public final class AssetManager implements AutoCloseable {
*
* @param cookie Identifier of the package to be opened.
* @param fileName Name of the asset to retrieve.
+ * @param usesFeatureFlags Whether the resources uses feature flags
* @hide
*/
- @NonNull XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName) throws IOException {
+ @NonNull XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName,
+ boolean usesFeatureFlags) throws IOException {
Objects.requireNonNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
@@ -1229,7 +1231,8 @@ public final class AssetManager implements AutoCloseable {
if (xmlBlock == 0) {
throw new FileNotFoundException("Asset XML file: " + fileName);
}
- final XmlBlock block = new XmlBlock(this, xmlBlock);
+
+ final XmlBlock block = new XmlBlock(this, xmlBlock, usesFeatureFlags);
incRefsLocked(block.hashCode());
return block;
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 2658efab0e44..92f8bb4e005e 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -2568,7 +2568,7 @@ public class Resources {
impl.getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
return loadXmlResourceParser(value.string.toString(), id,
- value.assetCookie, type);
+ value.assetCookie, type, value.usesFeatureFlags);
}
throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ " type #0x" + Integer.toHexString(value.type) + " is not valid");
@@ -2591,7 +2591,26 @@ public class Resources {
@UnsupportedAppUsage
XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie,
String type) throws NotFoundException {
- return mResourcesImpl.loadXmlResourceParser(file, id, assetCookie, type);
+ return loadXmlResourceParser(file, id, assetCookie, type, true);
+ }
+
+ /**
+ * Loads an XML parser for the specified file.
+ *
+ * @param file the path for the XML file to parse
+ * @param id the resource identifier for the file
+ * @param assetCookie the asset cookie for the file
+ * @param type the type of resource (used for logging)
+ * @param usesFeatureFlags whether the xml has read/write feature flags
+ * @return a parser for the specified XML file
+ * @throws NotFoundException if the file could not be loaded
+ * @hide
+ */
+ @NonNull
+ @VisibleForTesting
+ public XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie,
+ String type, boolean usesFeatureFlags) throws NotFoundException {
+ return mResourcesImpl.loadXmlResourceParser(file, id, assetCookie, type, usesFeatureFlags);
}
/**
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 8c76fd70afd9..6cbad2f0909b 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -276,7 +276,8 @@ public class ResourcesImpl {
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
- void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
+ @VisibleForTesting
+ public void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
if (found) {
@@ -1057,8 +1058,8 @@ public class ResourcesImpl {
int id, int density, String file)
throws IOException, XmlPullParserException {
try (
- XmlResourceParser rp =
- loadXmlResourceParser(file, id, value.assetCookie, "drawable")
+ XmlResourceParser rp = loadXmlResourceParser(
+ file, id, value.assetCookie, "drawable", value.usesFeatureFlags)
) {
return Drawable.createFromXmlForDensity(wrapper, rp, density, null);
}
@@ -1092,7 +1093,7 @@ public class ResourcesImpl {
try {
if (file.endsWith("xml")) {
final XmlResourceParser rp = loadXmlResourceParser(
- file, id, value.assetCookie, "font");
+ file, id, value.assetCookie, "font", value.usesFeatureFlags);
final FontResourcesParser.FamilyResourceEntry familyEntry =
FontResourcesParser.parse(rp, wrapper);
if (familyEntry == null) {
@@ -1286,7 +1287,7 @@ public class ResourcesImpl {
if (file.endsWith(".xml")) {
try {
final XmlResourceParser parser = loadXmlResourceParser(
- file, id, value.assetCookie, "ComplexColor");
+ file, id, value.assetCookie, "ComplexColor", value.usesFeatureFlags);
final AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
@@ -1331,12 +1332,13 @@ public class ResourcesImpl {
* @param id the resource identifier for the file
* @param assetCookie the asset cookie for the file
* @param type the type of resource (used for logging)
+ * @param usesFeatureFlags whether the xml has read/write feature flags
* @return a parser for the specified XML file
* @throws NotFoundException if the file could not be loaded
*/
@NonNull
XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
- @NonNull String type)
+ @NonNull String type, boolean usesFeatureFlags)
throws NotFoundException {
if (id != 0) {
try {
@@ -1355,7 +1357,8 @@ public class ResourcesImpl {
// Not in the cache, create a new block and put it at
// the next slot in the cache.
- final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);
+ final XmlBlock block =
+ mAssets.openXmlBlockAsset(assetCookie, file, usesFeatureFlags);
if (block != null) {
final int pos = (mLastCachedXmlBlockIndex + 1) % num;
mLastCachedXmlBlockIndex = pos;
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
index 36fa05905814..b27150b7171f 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -59,12 +59,14 @@ public final class XmlBlock implements AutoCloseable {
mAssets = null;
mNative = nativeCreate(data, 0, data.length);
mStrings = new StringBlock(nativeGetStringBlock(mNative), false);
+ mUsesFeatureFlags = true;
}
public XmlBlock(byte[] data, int offset, int size) {
mAssets = null;
mNative = nativeCreate(data, offset, size);
mStrings = new StringBlock(nativeGetStringBlock(mNative), false);
+ mUsesFeatureFlags = true;
}
@Override
@@ -346,7 +348,8 @@ public final class XmlBlock implements AutoCloseable {
if (ev == ERROR_BAD_DOCUMENT) {
throw new XmlPullParserException("Corrupt XML binary file");
}
- if (useLayoutReadwrite() && ev == START_TAG) {
+
+ if (useLayoutReadwrite() && mUsesFeatureFlags && ev == START_TAG) {
AconfigFlags flags = ParsingPackageUtils.getAconfigFlags();
if (flags.skipCurrentElement(/* pkg= */ null, this)) {
int depth = 1;
@@ -678,10 +681,11 @@ public final class XmlBlock implements AutoCloseable {
* are doing! The given native object must exist for the entire lifetime
* of this newly creating XmlBlock.
*/
- XmlBlock(@Nullable AssetManager assets, long xmlBlock) {
+ XmlBlock(@Nullable AssetManager assets, long xmlBlock, boolean usesFeatureFlags) {
mAssets = assets;
mNative = xmlBlock;
mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false);
+ mUsesFeatureFlags = usesFeatureFlags;
}
private @Nullable final AssetManager mAssets;
@@ -690,6 +694,8 @@ public final class XmlBlock implements AutoCloseable {
private boolean mOpen = true;
private int mOpenCount = 1;
+ private final boolean mUsesFeatureFlags;
+
private static final native long nativeCreate(byte[] data,
int offset,
int size);
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 4a9928532b93..8a64dd67ace9 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -4702,11 +4702,9 @@ public final class Parcel {
object = readValue(type, loader, clazz, itemTypes);
int actual = dataPosition() - start;
if (actual != length) {
- String error = "Unparcelling of " + object + " of type "
- + Parcel.valueTypeToString(type) + " consumed " + actual
- + " bytes, but " + length + " expected.";
- Slog.wtfStack(TAG, error);
- throw new BadParcelableException(error);
+ Slog.wtfStack(TAG,
+ "Unparcelling of " + object + " of type " + Parcel.valueTypeToString(type)
+ + " consumed " + actual + " bytes, but " + length + " expected.");
}
} else {
object = readValue(type, loader, clazz, itemTypes);
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index edfb78e59fe3..a2403826fe32 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -68,13 +68,6 @@ flag {
}
flag {
- name: "dump_attestation_verifications"
- namespace: "hardware_backed_security"
- description: "Add a dump capability for attestation_verification service"
- bug: "335498868"
-}
-
-flag {
name: "should_trust_manager_listen_for_primary_auth"
namespace: "biometrics"
description: "Causes TrustManagerService to listen for credential attempts and ignore reports from upstream"
diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java
index 26ab5885c9ea..11f3f8f68dd6 100644
--- a/core/java/android/util/TypedValue.java
+++ b/core/java/android/util/TypedValue.java
@@ -247,6 +247,12 @@ public class TypedValue {
*/
public int sourceResourceId;
+ /**
+ * Whether the value uses feature flags that need to be evaluated at runtime.
+ * @hide
+ */
+ public boolean usesFeatureFlags = false;
+
/* ------------------------------------------------------------ */
/** Return the data for this value as a float. Only use for values
diff --git a/core/java/android/view/RoundScrollbarRenderer.java b/core/java/android/view/RoundScrollbarRenderer.java
index 331e34526ae8..a592e1f0a874 100644
--- a/core/java/android/view/RoundScrollbarRenderer.java
+++ b/core/java/android/view/RoundScrollbarRenderer.java
@@ -45,8 +45,8 @@ public class RoundScrollbarRenderer {
private static final float MIN_SCROLLBAR_ANGLE_SWIPE = 0.3f * SCROLLBAR_ANGLE_RANGE;
private static final float GAP_BETWEEN_TRACK_AND_THUMB_DP = 3f;
private static final float OUTER_PADDING_DP = 2f;
- private static final int DEFAULT_THUMB_COLOR = 0xFFFFFFFF;
- private static final int DEFAULT_TRACK_COLOR = 0x4CFFFFFF;
+ private static final int DEFAULT_THUMB_COLOR = 0xFFC6C6C7;
+ private static final int DEFAULT_TRACK_COLOR = 0xFF2F3131;
// Rate at which the scrollbar will resize itself when the size of the view changes
private static final float RESIZING_RATE = 0.8f;
diff --git a/core/java/android/view/ScrollCaptureConnection.java b/core/java/android/view/ScrollCaptureConnection.java
index f0c7909647ce..0abb8b6c9a5a 100644
--- a/core/java/android/view/ScrollCaptureConnection.java
+++ b/core/java/android/view/ScrollCaptureConnection.java
@@ -20,8 +20,9 @@ import static android.os.Trace.TRACE_TAG_GRAPHICS;
import static java.util.Objects.requireNonNull;
-import android.annotation.BinderThread;
+import android.annotation.AnyThread;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UiThread;
import android.graphics.Point;
import android.graphics.Rect;
@@ -64,9 +65,13 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple
private final Executor mUiThread;
private final CloseGuard mCloseGuard = new CloseGuard();
+ @Nullable
private ScrollCaptureCallback mLocal;
+ @Nullable
private IScrollCaptureCallbacks mRemote;
+ @Nullable
private ScrollCaptureSession mSession;
+ @Nullable
private CancellationSignal mCancellation;
private volatile boolean mActive;
@@ -92,7 +97,7 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple
mPositionInWindow = new Point(selectedTarget.getPositionInWindow());
}
- @BinderThread
+ @AnyThread
@Override
public ICancellationSignal startCapture(@NonNull Surface surface,
@NonNull IScrollCaptureCallbacks remote) throws RemoteException {
@@ -115,7 +120,11 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple
Runnable listener =
SafeCallback.create(mCancellation, mUiThread, this::onStartCaptureCompleted);
// -> UiThread
- mUiThread.execute(() -> mLocal.onScrollCaptureStart(mSession, mCancellation, listener));
+ mUiThread.execute(() -> {
+ if (mLocal != null && mCancellation != null) {
+ mLocal.onScrollCaptureStart(mSession, mCancellation, listener);
+ }
+ });
return cancellation;
}
@@ -123,7 +132,11 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple
private void onStartCaptureCompleted() {
mActive = true;
try {
- mRemote.onCaptureStarted();
+ if (mRemote != null) {
+ mRemote.onCaptureStarted();
+ } else {
+ close();
+ }
} catch (RemoteException e) {
Log.w(TAG, "Shutting down due to error: ", e);
close();
@@ -132,7 +145,7 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple
Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, mTraceId);
}
- @BinderThread
+ @AnyThread
@Override
public ICancellationSignal requestImage(Rect requestRect) throws RemoteException {
Trace.asyncTraceForTrackBegin(TRACE_TAG_GRAPHICS, TRACE_TRACK, REQUEST_IMAGE, mTraceId);
@@ -145,7 +158,7 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple
SafeCallback.create(mCancellation, mUiThread, this::onImageRequestCompleted);
// -> UiThread
mUiThread.execute(() -> {
- if (mLocal != null) {
+ if (mLocal != null && mSession != null && mCancellation != null) {
mLocal.onScrollCaptureImageRequest(
mSession, mCancellation, new Rect(requestRect), listener);
}
@@ -157,7 +170,11 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple
@UiThread
void onImageRequestCompleted(Rect capturedArea) {
try {
- mRemote.onImageRequestCompleted(0, capturedArea);
+ if (mRemote != null) {
+ mRemote.onImageRequestCompleted(0, capturedArea);
+ } else {
+ close();
+ }
} catch (RemoteException e) {
Log.w(TAG, "Shutting down due to error: ", e);
close();
@@ -167,7 +184,7 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple
Trace.asyncTraceForTrackEnd(TRACE_TAG_GRAPHICS, TRACE_TRACK, mTraceId);
}
- @BinderThread
+ @AnyThread
@Override
public ICancellationSignal endCapture() throws RemoteException {
Trace.asyncTraceForTrackBegin(TRACE_TAG_GRAPHICS, TRACE_TRACK, END_CAPTURE, mTraceId);
@@ -212,7 +229,7 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple
}
- @BinderThread
+ @AnyThread
@Override
public synchronized void close() {
Trace.instantForTrack(TRACE_TAG_GRAPHICS, TRACE_TRACK, "close");
@@ -220,7 +237,11 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple
Log.w(TAG, "close(): capture session still active! Ending now.");
cancelPendingAction();
final ScrollCaptureCallback callback = mLocal;
- mUiThread.execute(() -> callback.onScrollCaptureEnd(() -> { /* ignore */ }));
+ mUiThread.execute(() -> {
+ if (callback != null) {
+ callback.onScrollCaptureEnd(() -> { /* ignore */ });
+ }
+ });
mActive = false;
}
if (mRemote != null) {
@@ -297,10 +318,13 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple
protected final void maybeAccept(Consumer<T> consumer) {
T value = mValue.getAndSet(null);
if (mSignal.isCanceled()) {
+ Log.w(TAG, "callback ignored, operation already cancelled");
return;
}
if (value != null) {
mExecutor.execute(() -> consumer.accept(value));
+ } else {
+ Log.w(TAG, "callback ignored, value already delivered");
}
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index c7ae3283c46c..f9d7a672f43a 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -2592,7 +2592,7 @@ public final class SurfaceControl implements Parcelable {
int[] dataspaces = nativeGetCompositionDataspaces();
ColorSpace srgb = ColorSpace.get(ColorSpace.Named.SRGB);
ColorSpace[] colorSpaces = { srgb, srgb };
- if (dataspaces.length == 2) {
+ if (dataspaces != null && dataspaces.length == 2) {
for (int i = 0; i < 2; ++i) {
ColorSpace cs = ColorSpace.getFromDataSpace(dataspaces[i]);
if (cs != null) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9a62045f3435..c275ed3a3b06 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -11518,12 +11518,24 @@ public final class ViewRootImpl implements ViewParent,
// Search through View-tree
View rootView = getView();
- if (rootView != null) {
- Point point = new Point();
- Rect rect = new Rect(0, 0, rootView.getWidth(), rootView.getHeight());
- getChildVisibleRect(rootView, rect, point);
- rootView.dispatchScrollCaptureSearch(rect, point, results::addTarget);
+ if (rootView == null) {
+ ScrollCaptureResponse.Builder response = new ScrollCaptureResponse.Builder();
+ response.setWindowTitle(getTitle().toString());
+ response.setPackageName(mContext.getPackageName());
+ response.setDescription("The root view was null");
+ try {
+ listener.onScrollCaptureResponse(response.build());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to send scroll capture search result", e);
+ }
+ return;
}
+
+ Point point = new Point();
+ Rect rect = new Rect(0, 0, rootView.getWidth(), rootView.getHeight());
+ getChildVisibleRect(rootView, rect, point);
+ rootView.dispatchScrollCaptureSearch(rect, point, results::addTarget);
+
Runnable onComplete = () -> dispatchScrollCaptureSearchResponse(listener, results);
results.setOnCompleteListener(onComplete);
if (!results.isComplete()) {
@@ -11548,6 +11560,16 @@ public final class ViewRootImpl implements ViewParent,
pw.flush();
response.addMessage(writer.toString());
+ if (mView == null) {
+ response.setDescription("The root view disappeared!");
+ try {
+ listener.onScrollCaptureResponse(response.build());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to send scroll capture search result", e);
+ }
+ return;
+ }
+
if (selectedTarget == null) {
response.setDescription("No scrollable targets found in window");
try {
@@ -11574,6 +11596,7 @@ public final class ViewRootImpl implements ViewParent,
boundsOnScreen.set(0, 0, mView.getWidth(), mView.getHeight());
boundsOnScreen.offset(mAttachInfo.mTmpLocation[0], mAttachInfo.mTmpLocation[1]);
response.setWindowBounds(boundsOnScreen);
+ Log.d(TAG, "ScrollCaptureSearchResponse: " + response);
// Create a connection and return it to the caller
ScrollCaptureConnection connection = new ScrollCaptureConnection(
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 9d21f1aff0c3..1ba3a74b8b2b 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -80,9 +80,6 @@ import static android.view.WindowLayoutParamsProto.WINDOW_ANIMATIONS;
import static android.view.WindowLayoutParamsProto.X;
import static android.view.WindowLayoutParamsProto.Y;
-import static com.android.hardware.input.Flags.FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW;
-import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
-
import android.Manifest.permission;
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
@@ -4549,29 +4546,6 @@ public interface WindowManager extends ViewManager {
public static final int INPUT_FEATURE_SENSITIVE_FOR_PRIVACY = 1 << 3;
/**
- * Input feature used to indicate that the system should send power key events to this
- * window when it's in the foreground. The window can override the double press power key
- * gesture behavior.
- *
- * A double press gesture is defined as two
- * {@link KeyEvent.Callback#onKeyDown(int, KeyEvent)} events within a time span defined by
- * {@link ViewConfiguration#getMultiPressTimeout()}.
- *
- * Note: While the window may receive all power key {@link KeyEvent}s, it can only
- * override the double press gesture behavior. The system will perform default behavior for
- * single, long-press and other multi-press gestures, regardless of if the app handles the
- * key or not.
- *
- * To override the default behavior for double press, the app must return true for the
- * second {@link KeyEvent.Callback#onKeyDown(int, KeyEvent)}. If the app returns false, the
- * system behavior will be performed for double press.
- * @hide
- */
- @RequiresPermission(permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public static final int
- INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS = 1 << 4;
-
- /**
* An internal annotation for flags that can be specified to {@link #inputFeatures}.
*
* NOTE: These are not the same as {@link android.os.InputConfig} flags.
@@ -4583,8 +4557,7 @@ public interface WindowManager extends ViewManager {
INPUT_FEATURE_NO_INPUT_CHANNEL,
INPUT_FEATURE_DISABLE_USER_ACTIVITY,
INPUT_FEATURE_SPY,
- INPUT_FEATURE_SENSITIVE_FOR_PRIVACY,
- INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS
+ INPUT_FEATURE_SENSITIVE_FOR_PRIVACY
})
public @interface InputFeatureFlags {
}
@@ -4874,44 +4847,6 @@ public interface WindowManager extends ViewManager {
}
/**
- * Specifies if the system should send power key events to this window when it's in the
- * foreground, with only the double tap gesture behavior being overrideable.
- *
- * @param enabled if true, the system should send power key events to this window when it's
- * in the foreground, with only the power key double tap gesture being
- * overrideable.
- * @hide
- */
- @SystemApi
- @RequiresPermission(permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- @FlaggedApi(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void setReceivePowerKeyDoublePressEnabled(boolean enabled) {
- if (enabled) {
- inputFeatures
- |= INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS;
- } else {
- inputFeatures
- &= ~INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS;
- }
- }
-
- /**
- * Returns whether or not the system should send power key events to this window when it's
- * in the foreground, with only the double tap gesture being overrideable.
- *
- * @return if the system should send power key events to this window when it's in the
- * foreground, with only the double tap gesture being overrideable.
- * @hide
- */
- @SystemApi
- @RequiresPermission(permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- @FlaggedApi(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public boolean isReceivePowerKeyDoublePressEnabled() {
- return (inputFeatures
- & INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS) != 0;
- }
-
- /**
* Specifies that the window should be considered a trusted system overlay. Trusted system
* overlays are ignored when considering whether windows are obscured during input
* dispatch. Requires the {@link android.Manifest.permission#INTERNAL_SYSTEM_WINDOW}
@@ -6312,16 +6247,6 @@ public interface WindowManager extends ViewManager {
inputFeatures &= ~INPUT_FEATURE_SPY;
features.add("INPUT_FEATURE_SPY");
}
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- if ((inputFeatures
- & INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS)
- != 0) {
- inputFeatures
- &=
- ~INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS;
- features.add("INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS");
- }
- }
if (inputFeatures != 0) {
features.add(Integer.toHexString(inputFeatures));
}
diff --git a/core/java/android/view/XrWindowProperties.java b/core/java/android/view/XrWindowProperties.java
index 23021a563393..c02d7a9bb8c5 100644
--- a/core/java/android/view/XrWindowProperties.java
+++ b/core/java/android/view/XrWindowProperties.java
@@ -27,7 +27,7 @@ public final class XrWindowProperties {
private XrWindowProperties() {}
/**
- * Both Application and activity level
+ * Application and Activity level
* {@link android.content.pm.PackageManager.Property PackageManager.Property} for an app to
* inform the system of the activity launch mode in XR. When it is declared at the application
* level, all activities are set to the defined value, unless it is overridden at the activity
@@ -105,7 +105,7 @@ public final class XrWindowProperties {
"XR_ACTIVITY_START_MODE_HOME_SPACE";
/**
- * Both Application and activity level
+ * Application and Activity level
* {@link android.content.pm.PackageManager.Property PackageManager.Property} for an app to
* inform the system of the type of safety boundary recommended for the activity. When it is
* declared at the application level, all activities are set to the defined value, unless it is
@@ -156,4 +156,30 @@ public final class XrWindowProperties {
*/
@FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
public static final String XR_BOUNDARY_TYPE_LARGE = "XR_BOUNDARY_TYPE_LARGE";
+
+ /**
+ * Application and Activity level
+ * {@link android.content.pm.PackageManager.Property PackageManager.Property} to inform the
+ * system if it should play a system provided default animation when the app requests to enter
+ * or exit <a
+ * href="https://developer.android.com/develop/xr/jetpack-xr-sdk/transition-home-space-to-full-space">managed
+ * full space mode</a> in XR. When set to {@code true}, the system provided default animation is
+ * not played and the app is responsible for playing a custom enter or exit animation. When it
+ * is declared at the application level, all activities are set to the defined value, unless it
+ * is overridden at the activity level.
+ *
+ * <p>The default value is {@code false}.
+ *
+ * <p><b>Syntax:</b>
+ * <pre>
+ * &lt;application&gt;
+ * &lt;property
+ * android:name="android.window.PROPERTY_XR_USES_CUSTOM_FULL_SPACE_MANAGED_ANIMATION"
+ * android:value="false|true"/&gt;
+ * &lt;/application&gt;
+ * </pre>
+ */
+ @FlaggedApi(android.xr.Flags.FLAG_XR_MANIFEST_ENTRIES)
+ public static final String PROPERTY_XR_USES_CUSTOM_FULL_SPACE_MANAGED_ANIMATION =
+ "android.window.PROPERTY_XR_USES_CUSTOM_FULL_SPACE_MANAGED_ANIMATION";
}
diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java
index 983be682b8aa..527fcdf852f3 100644
--- a/core/java/android/window/DesktopModeFlags.java
+++ b/core/java/android/window/DesktopModeFlags.java
@@ -64,7 +64,7 @@ public enum DesktopModeFlags {
ENABLE_DESKTOP_COMPAT_UI_VISIBILITY_STATUS(Flags::enableCompatUiVisibilityStatus, true),
ENABLE_DESKTOP_IMMERSIVE_DRAG_BUGFIX(Flags::enableDesktopImmersiveDragBugfix, true),
ENABLE_DESKTOP_INDICATOR_IN_SEPARATE_THREAD_BUGFIX(
- Flags::enableDesktopIndicatorInSeparateThreadBugfix, false),
+ Flags::enableDesktopIndicatorInSeparateThreadBugfix, true),
ENABLE_DESKTOP_OPENING_DEEPLINK_MINIMIZE_ANIMATION_BUGFIX(
Flags::enableDesktopOpeningDeeplinkMinimizeAnimationBugfix, true),
ENABLE_DESKTOP_RECENTS_TRANSITIONS_CORNERS_BUGFIX(
diff --git a/core/java/com/android/internal/policy/KeyInterceptionInfo.java b/core/java/com/android/internal/policy/KeyInterceptionInfo.java
index fed8fe3b4cc0..b20f6d225b69 100644
--- a/core/java/com/android/internal/policy/KeyInterceptionInfo.java
+++ b/core/java/com/android/internal/policy/KeyInterceptionInfo.java
@@ -27,13 +27,11 @@ public class KeyInterceptionInfo {
// Debug friendly name to help identify the window
public final String windowTitle;
public final int windowOwnerUid;
- public final int inputFeaturesFlags;
- public KeyInterceptionInfo(int type, int flags, String title, int uid, int inputFeaturesFlags) {
+ public KeyInterceptionInfo(int type, int flags, String title, int uid) {
layoutParamsType = type;
layoutParamsPrivateFlags = flags;
windowTitle = title;
windowOwnerUid = uid;
- this.inputFeaturesFlags = inputFeaturesFlags;
}
}
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index d35072fc10c3..6f1d72944a55 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -41,7 +41,6 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.hardware.input.InputManagerGlobal;
import android.os.Build;
@@ -77,7 +76,6 @@ import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.HashMap;
import java.util.List;
/**
@@ -240,8 +238,6 @@ public class LockPatternUtils {
private final SparseLongArray mLockoutDeadlines = new SparseLongArray();
private Boolean mHasSecureLockScreen;
- private HashMap<UserHandle, UserManager> mUserManagerCache = new HashMap<>();
-
/**
* Use {@link TrustManager#isTrustUsuallyManaged(int)}.
*
@@ -363,22 +359,6 @@ public class LockPatternUtils {
return mUserManager;
}
- private UserManager getUserManager(int userId) {
- UserHandle userHandle = UserHandle.of(userId);
- if (mUserManagerCache.containsKey(userHandle)) {
- return mUserManagerCache.get(userHandle);
- }
-
- try {
- Context userContext = mContext.createPackageContextAsUser("system", 0, userHandle);
- UserManager userManager = userContext.getSystemService(UserManager.class);
- mUserManagerCache.put(userHandle, userManager);
- return userManager;
- } catch (PackageManager.NameNotFoundException e) {
- throw new RuntimeException("Failed to create context for user " + userHandle, e);
- }
- }
-
private TrustManager getTrustManager() {
TrustManager trust = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
if (trust == null) {
@@ -966,7 +946,7 @@ public class LockPatternUtils {
*/
public void setSeparateProfileChallengeEnabled(int userHandle, boolean enabled,
LockscreenCredential profilePassword) {
- if (!isCredentialSharableWithParent(userHandle)) {
+ if (!isCredentialShareableWithParent(userHandle)) {
return;
}
try {
@@ -985,7 +965,7 @@ public class LockPatternUtils {
* credential is not shareable with its parent, or a non-profile user.
*/
public boolean isSeparateProfileChallengeEnabled(int userHandle) {
- return isCredentialSharableWithParent(userHandle) && hasSeparateChallenge(userHandle);
+ return isCredentialShareableWithParent(userHandle) && hasSeparateChallenge(userHandle);
}
/**
@@ -995,7 +975,7 @@ public class LockPatternUtils {
* credential is not shareable with its parent, or a non-profile user.
*/
public boolean isProfileWithUnifiedChallenge(int userHandle) {
- return isCredentialSharableWithParent(userHandle) && !hasSeparateChallenge(userHandle);
+ return isCredentialShareableWithParent(userHandle) && !hasSeparateChallenge(userHandle);
}
/**
@@ -1020,8 +1000,13 @@ public class LockPatternUtils {
return info != null && info.isManagedProfile();
}
- private boolean isCredentialSharableWithParent(int userHandle) {
- return getUserManager(userHandle).isCredentialSharableWithParent();
+ private boolean isCredentialShareableWithParent(int userHandle) {
+ try {
+ return getUserManager().getUserProperties(UserHandle.of(userHandle))
+ .isCredentialShareableWithParent();
+ } catch (IllegalArgumentException e) {
+ return false;
+ }
}
/**
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 7ed73d7668b9..40f6acceecb1 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -488,6 +488,7 @@ cc_library_shared_for_libandroid_runtime {
"libbinder",
"libbinder_ndk",
"libhidlbase", // libhwbinder is in here
+ "libaconfig_storage_read_api_cc",
],
version_script: "platform/linux/libandroid_runtime_export.txt",
},
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 1394b9f8781a..d3d838a1675b 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -67,6 +67,7 @@ static struct typedvalue_offsets_t {
jfieldID mResourceId;
jfieldID mChangingConfigurations;
jfieldID mDensity;
+ jfieldID mUsesFeatureFlags;
} gTypedValueOffsets;
// This is also used by asset_manager.cpp.
@@ -137,6 +138,8 @@ static jint CopyValue(JNIEnv* env, const AssetManager2::SelectedValue& value,
env->SetIntField(out_typed_value, gTypedValueOffsets.mResourceId, value.resid);
env->SetIntField(out_typed_value, gTypedValueOffsets.mChangingConfigurations, value.flags);
env->SetIntField(out_typed_value, gTypedValueOffsets.mDensity, value.config.density);
+ env->SetBooleanField(out_typed_value, gTypedValueOffsets.mUsesFeatureFlags,
+ value.entry_flags & ResTable_entry::FLAG_USES_FEATURE_FLAGS);
return static_cast<jint>(ApkAssetsCookieToJavaCookie(value.cookie));
}
@@ -1664,6 +1667,7 @@ int register_android_content_AssetManager(JNIEnv* env) {
gTypedValueOffsets.mChangingConfigurations =
GetFieldIDOrDie(env, typedValue, "changingConfigurations", "I");
gTypedValueOffsets.mDensity = GetFieldIDOrDie(env, typedValue, "density", "I");
+ gTypedValueOffsets.mUsesFeatureFlags = GetFieldIDOrDie(env, typedValue, "usesFeatureFlags", "Z");
jclass assetManager = FindClassOrDie(env, "android/content/res/AssetManager");
gAssetManagerOffsets.mObject = GetFieldIDOrDie(env, assetManager, "mObject", "J");
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 5820c8e947c2..3ebb48041ecd 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -471,6 +471,8 @@ message WindowStateProto {
repeated .android.view.InsetsSourceProto mergedLocalInsetsSources = 47;
optional int32 requested_visible_types = 48;
optional .android.graphics.RectProto dim_bounds = 49;
+ optional int32 prepare_sync_seq_id = 50;
+ optional int32 sync_seq_id = 51;
}
message IdentifierProto {
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 1199d77d04c6..29da0d6f67ae 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -181,6 +181,7 @@ android_app {
"ranging_aconfig_flags",
"aconfig_settingslib_flags",
"telephony_flags",
+ "update_engine_aconfig_declarations",
],
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f62ce278f28a..636968dd1152 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5180,6 +5180,13 @@
<permission android:name="android.permission.READ_LOGS"
android:protectionLevel="signature|privileged|development" />
+ <!-- Allows an application to read the update_engine logs
+ <p>Not for use by third-party applications.
+ @FlaggedApi("com.android.update_engine.minor_changes_2025q4") -->
+ <permission android:name="android.permission.READ_UPDATE_ENGINE_LOGS"
+ android:protectionLevel="signature|privileged|development"
+ android:featureFlag="com.android.update_engine.minor_changes_2025q4" />
+
<!-- Configure an application for debugging.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.SET_DEBUG_APP"
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 36564cd90d05..9b3a6cba5f23 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -358,7 +358,7 @@
<!-- USA: 5-6 digits (premium codes from https://www.premiumsmsrefunds.com/ShortCodes.htm),
visual voicemail code for T-Mobile: 122 -->
- <shortcode country="us" pattern="\\d{5,6}" free="122|\\d{5,6}" />
+ <shortcode country="us" pattern="\\d{5,6}" premium="20433|21(?:344|472)|22715|23(?:333|847)|24(?:15|28)0|25209|27(?:449|606|663)|28498|305(?:00|83)|32(?:340|941)|33(?:166|786|849)|34746|35(?:182|564)|37975|38(?:135|146|254)|41(?:366|463)|42335|43(?:355|500)|44(?:578|711|811)|45814|46(?:157|173|327)|46666|47553|48(?:221|277|669)|50(?:844|920)|51(?:062|368)|52944|54(?:723|892)|55928|56483|57370|59(?:182|187|252|342)|60339|61(?:266|982)|62478|64(?:219|898)|65(?:108|500)|69(?:208|388)|70877|71851|72(?:078|087|465)|73(?:288|588|882|909|997)|74(?:034|332|815)|76426|79213|81946|83177|84(?:103|685)|85797|86(?:234|236|666)|89616|90(?:715|842|938)|91(?:362|958)|94719|95297|96(?:040|666|835|969)|97(?:142|294|688)|99(?:689|796|807)" standard="44567|244444" free="122|87902|21696|24614|28003|30356|33669|40196|41064|41270|43753|44034|46645|52413|56139|57969|61785|66975|75136|76227|81398|83952|85140|86566|86799|95737|96684|99245|611611|96831|10907" />
<!--Uruguay : 1-6 digits (standard system default, not country specific) -->
<shortcode country="uy" pattern="\\d{1,6}" free="55002|191289" />
diff --git a/core/tests/coretests/res/xml/flags.xml b/core/tests/coretests/res/xml/flags.xml
new file mode 100644
index 000000000000..e580ea5dea00
--- /dev/null
+++ b/core/tests/coretests/res/xml/flags.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<first xmlns:android="http://schemas.android.com/apk/res/android">
+ <second android:featureFlag="android.content.res.always_false"/>
+</first> \ No newline at end of file
diff --git a/core/tests/coretests/src/android/content/res/XmlResourcesFlaggedTest.kt b/core/tests/coretests/src/android/content/res/XmlResourcesFlaggedTest.kt
new file mode 100644
index 000000000000..8c20ba0d7fbe
--- /dev/null
+++ b/core/tests/coretests/src/android/content/res/XmlResourcesFlaggedTest.kt
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.res
+
+import android.platform.test.annotations.Presubmit
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import android.util.TypedValue
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+
+import com.android.frameworks.coretests.R
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils
+
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+
+
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlPullParserException
+
+import java.io.IOException
+
+/**
+* Tests for flag handling within Resources.loadXmlResourceParser() and methods that call it.
+*/
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class XmlResourcesFlaggedTest {
+ @get:Rule
+ val mCheckFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+ private var mResources: Resources = Resources(null)
+
+ @Before
+ fun setup() {
+ mResources = InstrumentationRegistry.getInstrumentation().getContext().getResources()
+ mResources.getImpl().flushLayoutCache()
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_LAYOUT_READWRITE_FLAGS)
+ fun flaggedXmlTypedValueMarkedAsSuch() {
+ val tv = TypedValue()
+ mResources.getImpl().getValue(R.xml.flags, tv, false)
+ assertTrue(tv.usesFeatureFlags)
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_LAYOUT_READWRITE_FLAGS)
+ @Throws(IOException::class, XmlPullParserException::class)
+ fun parsedFlaggedXmlWithTrueOneElement() {
+ ParsingPackageUtils.getAconfigFlags()
+ .addFlagValuesForTesting(mapOf("android.content.res.always_false" to false))
+ val tv = TypedValue()
+ mResources.getImpl().getValue(R.xml.flags, tv, false)
+ val parser = mResources.loadXmlResourceParser(
+ tv.string.toString(),
+ R.xml.flags,
+ tv.assetCookie,
+ "xml",
+ true
+ )
+ assertEquals(XmlPullParser.START_DOCUMENT, parser.next())
+ assertEquals(XmlPullParser.START_TAG, parser.next())
+ assertEquals("first", parser.getName())
+ assertEquals(XmlPullParser.END_TAG, parser.next())
+ assertEquals(XmlPullParser.END_DOCUMENT, parser.next())
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_LAYOUT_READWRITE_FLAGS)
+ @Throws(IOException::class, XmlPullParserException::class)
+ fun parsedFlaggedXmlWithFalseTwoElements() {
+ val tv = TypedValue()
+ mResources.getImpl().getValue(R.xml.flags, tv, false)
+ val parser = mResources.loadXmlResourceParser(
+ tv.string.toString(),
+ R.xml.flags,
+ tv.assetCookie,
+ "xml",
+ false
+ )
+ assertEquals(XmlPullParser.START_DOCUMENT, parser.next())
+ assertEquals(XmlPullParser.START_TAG, parser.next())
+ assertEquals("first", parser.getName())
+ assertEquals(XmlPullParser.START_TAG, parser.next())
+ assertEquals("second", parser.getName())
+ assertEquals(XmlPullParser.END_TAG, parser.next())
+ assertEquals(XmlPullParser.END_TAG, parser.next())
+ assertEquals(XmlPullParser.END_DOCUMENT, parser.next())
+ }
+} \ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
index bee5dc4bf3c0..81954cb9a1a9 100644
--- a/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
+++ b/core/tests/coretests/src/android/view/ScrollCaptureConnectionTest.java
@@ -16,8 +16,6 @@
package android.view;
-import static androidx.test.InstrumentationRegistry.getTargetContext;
-
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -32,7 +30,6 @@ import static org.mockito.Mockito.when;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Binder;
-import android.os.Handler;
import android.os.IBinder;
import android.os.ICancellationSignal;
import android.os.RemoteException;
@@ -54,7 +51,6 @@ import java.util.concurrent.Executor;
/**
* Tests of {@link ScrollCaptureConnection}.
*/
-@SuppressWarnings("UnnecessaryLocalVariable")
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -68,9 +64,8 @@ public class ScrollCaptureConnectionTest {
private ScrollCaptureTarget mTarget;
private ScrollCaptureConnection mConnection;
- private IBinder mConnectionBinder = new Binder("ScrollCaptureConnection Test");
+ private final IBinder mConnectionBinder = new Binder("ScrollCaptureConnection Test");
- private Handler mHandler;
@Mock
private Surface mSurface;
@@ -85,7 +80,6 @@ public class ScrollCaptureConnectionTest {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mHandler = new Handler(getTargetContext().getMainLooper());
when(mSurface.isValid()).thenReturn(true);
when(mView.getScrollCaptureHint()).thenReturn(View.SCROLL_CAPTURE_HINT_INCLUDE);
when(mRemote.asBinder()).thenReturn(mConnectionBinder);
@@ -269,8 +263,68 @@ public class ScrollCaptureConnectionTest {
assertFalse(mConnection.isConnected());
}
+ @Test(expected = RemoteException.class)
+ public void testRequestImage_beforeStarted() throws RemoteException {
+ mConnection.requestImage(new Rect(0, 1, 2, 3));
+ }
+
+
+ @Test(expected = RemoteException.class)
+ public void testRequestImage_beforeStartCompleted() throws RemoteException {
+ mFakeUiThread.setImmediate(false);
+ mConnection.startCapture(mSurface, mRemote);
+ mConnection.requestImage(new Rect(0, 1, 2, 3));
+ mFakeUiThread.runAll();
+ }
+
+ @Test
+ public void testCompleteStart_afterClosing() throws RemoteException {
+ mConnection.startCapture(mSurface, mRemote);
+ mConnection.close();
+ mFakeUiThread.setImmediate(false);
+ mCallback.completeStartRequest();
+ mFakeUiThread.runAll();
+ }
+
+ @Test
+ public void testLateCallbacks() throws RemoteException {
+ mConnection.startCapture(mSurface, mRemote);
+ mCallback.completeStartRequest();
+ mConnection.requestImage(new Rect(1, 2, 3, 4));
+ mConnection.endCapture();
+ mFakeUiThread.setImmediate(false);
+ mCallback.completeImageRequest(new Rect(1, 2, 3, 4));
+ mCallback.completeEndRequest();
+ mFakeUiThread.runAll();
+ }
+
+ @Test
+ public void testDelayedClose() throws RemoteException {
+ mConnection.startCapture(mSurface, mRemote);
+ mCallback.completeStartRequest();
+ mFakeUiThread.setImmediate(false);
+ mConnection.endCapture();
+ mFakeUiThread.runAll();
+ mConnection.close();
+ mCallback.completeEndRequest();
+ mFakeUiThread.runAll();
+ }
+
+ @Test
+ public void testRequestImage_delayedCancellation() throws Exception {
+ mConnection.startCapture(mSurface, mRemote);
+ mCallback.completeStartRequest();
+
+ ICancellationSignal signal = mConnection.requestImage(new Rect(1, 2, 3, 4));
+ mFakeUiThread.setImmediate(false);
+
+ signal.cancel();
+ mCallback.completeImageRequest(new Rect(1, 2, 3, 4));
+ }
+
+
static class FakeExecutor implements Executor {
- private Queue<Runnable> mQueue = new ArrayDeque<>();
+ private final Queue<Runnable> mQueue = new ArrayDeque<>();
private boolean mImmediate;
@Override
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index ca20aebf95d8..ea1ce48fe001 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -62,6 +62,12 @@
<permission name="android.permission.READ_LOGS" >
<group gid="log" />
+ <group gid="update_engine_log" />
+ </permission>
+
+ <permission name="android.permission.READ_UPDATE_ENGINE_LOGS"
+ featureFlag="com.android.update_engine.minor_changes_2025q4" >
+ <group gid="update_engine_log" />
</permission>
<permission name="android.permission.ACCESS_MTP" >
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/AppHandleAndHeaderVisibilityHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/AppHandleAndHeaderVisibilityHelper.kt
index 39ccf5bd03a7..950eeccf6a4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/AppHandleAndHeaderVisibilityHelper.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/AppHandleAndHeaderVisibilityHelper.kt
@@ -23,6 +23,7 @@ import android.view.WindowManager
import android.window.DesktopExperienceFlags.ENABLE_BUG_FIXES_FOR_SECONDARY_DISPLAY
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.desktopmode.DesktopWallpaperActivity.Companion.isWallpaperTask
+import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper
import com.android.wm.shell.shared.desktopmode.DesktopModeCompatPolicy
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
import com.android.wm.shell.splitscreen.SplitScreenController
@@ -52,7 +53,8 @@ class AppHandleAndHeaderVisibilityHelper (
private fun allowedForTask(taskInfo: ActivityManager.RunningTaskInfo): Boolean {
// TODO (b/382023296): Remove once we no longer rely on
// Flags.enableBugFixesForSecondaryDisplay as it is taken care of in #allowedForDisplay
- if (displayController.getDisplay(taskInfo.displayId) == null) {
+ val display = displayController.getDisplay(taskInfo.displayId)
+ if (display == null) {
// If DisplayController doesn't have it tracked, it could be a private/managed display.
return false
}
@@ -68,8 +70,7 @@ class AppHandleAndHeaderVisibilityHelper (
// TODO (b/382023296): Remove once we no longer rely on
// Flags.enableBugFixesForSecondaryDisplay as it is taken care of in #allowedForDisplay
val isOnLargeScreen =
- displayController.getDisplay(taskInfo.displayId).minSizeDimensionDp >=
- WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
+ display.minSizeDimensionDp >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP
if (!DesktopModeStatus.canEnterDesktopMode(context)
&& DesktopModeStatus.overridesShowAppHandle(context)
&& !isOnLargeScreen
@@ -78,6 +79,14 @@ class AppHandleAndHeaderVisibilityHelper (
// small screens
return false
}
+ if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()
+ && !DesktopModeStatus.isDesktopModeSupportedOnDisplay(context, display)
+ ) {
+ // TODO(b/388853233): enable handles for split tasks once drag to bubble is enabled
+ if (taskInfo.windowingMode != WindowConfiguration.WINDOWING_MODE_FULLSCREEN) {
+ return false
+ }
+ }
return DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(context)
&& !isWallpaperTask(taskInfo)
&& taskInfo.windowingMode != WindowConfiguration.WINDOWING_MODE_PINNED
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index e09ab5fd1643..6caae4c7623e 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -82,6 +82,9 @@ struct FindEntryResult {
// The bitmask of configuration axis with which the resource value varies.
uint32_t type_flags;
+ // The bitmask of ResTable_entry flags
+ uint16_t entry_flags;
+
// The dynamic package ID map for the package from which this resource came from.
const DynamicRefTable* dynamic_ref_table;
@@ -1031,6 +1034,7 @@ base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
.entry = *entry,
.config = *best_config,
.type_flags = type_flags,
+ .entry_flags = (*best_entry_verified)->flags(),
.dynamic_ref_table = package_group.dynamic_ref_table.get(),
.package_name = &best_package->GetPackageName(),
.type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1),
@@ -1185,16 +1189,16 @@ base::expected<AssetManager2::SelectedValue, NullOrIOError> AssetManager2::GetRe
}
// Create a reference since we can't represent this complex type as a Res_value.
- return SelectedValue(Res_value::TYPE_REFERENCE, resid, result->cookie, result->type_flags,
- resid, result->config);
+ return SelectedValue(Res_value::TYPE_REFERENCE, resid, result->cookie, result->entry_flags,
+ result->type_flags, resid, result->config);
}
// Convert the package ID to the runtime assigned package ID.
Res_value value = std::get<Res_value>(result->entry);
result->dynamic_ref_table->lookupResourceValue(&value);
- return SelectedValue(value.dataType, value.data, result->cookie, result->type_flags,
- resid, result->config);
+ return SelectedValue(value.dataType, value.data, result->cookie, result->entry_flags,
+ result->type_flags, resid, result->config);
}
base::expected<std::monostate, NullOrIOError> AssetManager2::ResolveReference(
@@ -1847,8 +1851,8 @@ std::optional<AssetManager2::SelectedValue> Theme::GetAttribute(uint32_t resid)
}
return AssetManager2::SelectedValue(entry_it->value.dataType, entry_it->value.data,
- entry_it->cookie, type_spec_flags, 0U /* resid */,
- {} /* config */);
+ entry_it->cookie, 0U /* entry flags*/, type_spec_flags,
+ 0U /* resid */, {} /* config */);
}
return std::nullopt;
}
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index b0179524f6cd..ffcef944a6ba 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -257,6 +257,7 @@ class AssetManager2 {
: cookie(entry.cookie),
data(entry.value.data),
type(entry.value.dataType),
+ entry_flags(0U),
flags(bag->type_spec_flags),
resid(0U),
config() {
@@ -271,6 +272,9 @@ class AssetManager2 {
// Type of the data value.
uint8_t type;
+ // The bitmask of ResTable_entry flags
+ uint16_t entry_flags;
+
// The bitmask of configuration axis that this resource varies with.
// See ResTable_config::CONFIG_*.
uint32_t flags;
@@ -283,9 +287,10 @@ class AssetManager2 {
private:
SelectedValue(uint8_t value_type, Res_value::data_type value_data, ApkAssetsCookie cookie,
- uint32_t type_flags, uint32_t resid, ResTable_config config) :
- cookie(cookie), data(value_data), type(value_type), flags(type_flags),
- resid(resid), config(std::move(config)) {}
+ uint16_t entry_flags, uint32_t type_flags, uint32_t resid, ResTable_config config)
+ :
+ cookie(cookie), data(value_data), type(value_type), entry_flags(entry_flags),
+ flags(type_flags), resid(resid), config(std::move(config)) {}
};
// Retrieves the best matching resource value with ID `resid`.
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 3f228841f6ba..948437230ecc 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -23,6 +23,7 @@
#include "androidfw/ResourceUtils.h"
#include "data/appaslib/R.h"
#include "data/basic/R.h"
+#include "data/flagged/R.h"
#include "data/lib_one/R.h"
#include "data/lib_two/R.h"
#include "data/libclient/R.h"
@@ -32,6 +33,7 @@
namespace app = com::android::app;
namespace appaslib = com::android::appaslib::app;
namespace basic = com::android::basic;
+namespace flagged = com::android::flagged;
namespace lib_one = com::android::lib_one;
namespace lib_two = com::android::lib_two;
namespace libclient = com::android::libclient;
@@ -87,6 +89,10 @@ class AssetManager2Test : public ::testing::Test {
overlayable_assets_ = ApkAssets::Load("overlayable/overlayable.apk");
ASSERT_THAT(overlayable_assets_, NotNull());
+
+ flagged_assets_ = ApkAssets::Load("flagged/flagged.apk");
+ ASSERT_THAT(app_assets_, NotNull());
+
chdir(original_path.c_str());
}
@@ -104,6 +110,7 @@ class AssetManager2Test : public ::testing::Test {
AssetManager2::ApkAssetsPtr app_assets_;
AssetManager2::ApkAssetsPtr overlay_assets_;
AssetManager2::ApkAssetsPtr overlayable_assets_;
+ AssetManager2::ApkAssetsPtr flagged_assets_;
};
TEST_F(AssetManager2Test, FindsResourceFromSingleApkAssets) {
@@ -856,4 +863,12 @@ TEST_F(AssetManager2Test, GetApkAssets) {
EXPECT_EQ(1, lib_one_assets_->getStrongCount());
}
+TEST_F(AssetManager2Test, GetFlaggedAssets) {
+ AssetManager2 assetmanager;
+ assetmanager.SetApkAssets({flagged_assets_});
+ auto value = assetmanager.GetResource(flagged::R::xml::flagged, false, 0);
+ ASSERT_TRUE(value.has_value());
+ EXPECT_TRUE(value->entry_flags & ResTable_entry::FLAG_USES_FEATURE_FLAGS);
+}
+
} // namespace android
diff --git a/libs/androidfw/tests/data/flagged/AndroidManifest.xml b/libs/androidfw/tests/data/flagged/AndroidManifest.xml
new file mode 100644
index 000000000000..cc1394328797
--- /dev/null
+++ b/libs/androidfw/tests/data/flagged/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.basic">
+ <application />
+</manifest>
diff --git a/libs/androidfw/tests/data/flagged/R.h b/libs/androidfw/tests/data/flagged/R.h
new file mode 100644
index 000000000000..33ccab28cdd3
--- /dev/null
+++ b/libs/androidfw/tests/data/flagged/R.h
@@ -0,0 +1,35 @@
+/*
+* Copyright (C) 2025 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+#pragma once
+
+#include <cstdint>
+
+namespace com {
+namespace android {
+namespace flagged {
+
+struct R {
+ struct xml {
+ enum : uint32_t {
+ flagged = 0x7f010000,
+ };
+ };
+};
+
+} // namespace flagged
+} // namespace android
+} // namespace com \ No newline at end of file
diff --git a/libs/androidfw/tests/data/flagged/build b/libs/androidfw/tests/data/flagged/build
new file mode 100755
index 000000000000..9e5d21ba1833
--- /dev/null
+++ b/libs/androidfw/tests/data/flagged/build
@@ -0,0 +1,28 @@
+#!/bin/bash
+#
+# Copyright (C) 2025 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+set -e
+
+PATH_TO_FRAMEWORK_RES=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/public/android.jar
+
+aapt2 compile --dir res -o compiled.flata
+aapt2 link -o flagged.apk \
+ --manifest AndroidManifest.xml \
+ -I $PATH_TO_FRAMEWORK_RES \
+ -I ../basic/basic.apk \
+ compiled.flata
+rm compiled.flata
diff --git a/libs/androidfw/tests/data/flagged/flagged.apk b/libs/androidfw/tests/data/flagged/flagged.apk
new file mode 100644
index 000000000000..94b8f4d9fcf0
--- /dev/null
+++ b/libs/androidfw/tests/data/flagged/flagged.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/flagged/res/xml/flagged.xml b/libs/androidfw/tests/data/flagged/res/xml/flagged.xml
new file mode 100644
index 000000000000..5fe8d1b3ac27
--- /dev/null
+++ b/libs/androidfw/tests/data/flagged/res/xml/flagged.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<first xmlns:android="http://schemas.android.com/apk/res/android">
+ <second android:featureFlag="android.content.res.always_false"/>
+</first> \ No newline at end of file
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index ab1be7e6128d..1bde5ff43aa8 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -168,6 +168,14 @@ cc_defaults {
"libutils",
],
},
+ host_linux: {
+ shared_libs: [
+ "libaconfig_storage_read_api_cc",
+ ],
+ whole_static_libs: [
+ "hwui_flags_cc_lib",
+ ],
+ },
},
}
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index b6a2ad7064a9..1a258e022dd0 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -2,6 +2,9 @@
#include "Bitmap.h"
#include <android-base/unique_fd.h>
+#ifdef __linux__
+#include <com_android_graphics_hwui_flags.h>
+#endif
#include <hwui/Bitmap.h>
#include <hwui/Paint.h>
#include <inttypes.h>
@@ -33,15 +36,6 @@
#endif
#include "android_nio_utils.h"
-#ifdef __ANDROID__
-#include <com_android_graphics_hwui_flags.h>
-namespace hwui_flags = com::android::graphics::hwui::flags;
-#else
-namespace hwui_flags {
-constexpr bool bitmap_parcel_ashmem_as_immutable() { return false; }
-}
-#endif
-
#define DEBUG_PARCEL 0
static jclass gBitmap_class;
@@ -861,7 +855,7 @@ static bool shouldParcelAsMutable(SkBitmap& bitmap, AParcel* parcel) {
return false;
}
- if (!hwui_flags::bitmap_parcel_ashmem_as_immutable()) {
+ if (!com::android::graphics::hwui::flags::bitmap_parcel_ashmem_as_immutable()) {
return true;
}
diff --git a/media/java/android/media/projection/MediaProjectionAppContent.aidl b/media/java/android/media/projection/MediaProjectionAppContent.aidl
new file mode 100644
index 000000000000..6ead69b9fdc6
--- /dev/null
+++ b/media/java/android/media/projection/MediaProjectionAppContent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+parcelable MediaProjectionAppContent; \ No newline at end of file
diff --git a/media/java/android/media/projection/MediaProjectionAppContent.java b/media/java/android/media/projection/MediaProjectionAppContent.java
new file mode 100644
index 000000000000..da0bdc191c0c
--- /dev/null
+++ b/media/java/android/media/projection/MediaProjectionAppContent.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+import android.annotation.FlaggedApi;
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * Holds information about content an app can share via the MediaProjection APIs.
+ * <p>
+ * An application requesting a {@link MediaProjection session} can add its own content in the
+ * list of available content along with the whole screen or a single application.
+ * <p>
+ * Each instance of {@link MediaProjectionAppContent} contains an id that is used to identify the
+ * content chosen by the user back to the advertising application, thus the meaning of the id is
+ * only relevant to that application.
+ */
+@FlaggedApi(com.android.media.projection.flags.Flags.FLAG_APP_CONTENT_SHARING)
+public final class MediaProjectionAppContent implements Parcelable {
+
+ private final Bitmap mThumbnail;
+ private final CharSequence mTitle;
+ private final int mId;
+
+ /**
+ * Constructor to pass a thumbnail, title and id.
+ *
+ * @param thumbnail The thumbnail representing this content to be shown to the user.
+ * @param title A user visible string representing the title of this content.
+ * @param id An arbitrary int defined by the advertising application to be fed back once
+ * the user made their choice.
+ */
+ public MediaProjectionAppContent(@NonNull Bitmap thumbnail, @NonNull CharSequence title,
+ int id) {
+ mThumbnail = Objects.requireNonNull(thumbnail, "thumbnail can't be null").asShared();
+ mTitle = Objects.requireNonNull(title, "title can't be null");
+ mId = id;
+ }
+
+ /**
+ * Returns thumbnail representing this content to be shown to the user.
+ *
+ * @hide
+ */
+ @NonNull
+ public Bitmap getThumbnail() {
+ return mThumbnail;
+ }
+
+ /**
+ * Returns user visible string representing the title of this content.
+ *
+ * @hide
+ */
+ @NonNull
+ public CharSequence getTitle() {
+ return mTitle;
+ }
+
+ /**
+ * Returns the arbitrary int defined by the advertising application to be fed back once
+ * the user made their choice.
+ *
+ * @hide
+ */
+ public int getId() {
+ return mId;
+ }
+
+ private MediaProjectionAppContent(Parcel in) {
+ mThumbnail = in.readParcelable(this.getClass().getClassLoader(), Bitmap.class);
+ mTitle = in.readCharSequence();
+ mId = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mThumbnail, flags);
+ dest.writeCharSequence(mTitle);
+ dest.writeInt(mId);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @NonNull
+ public static final Creator<MediaProjectionAppContent> CREATOR =
+ new Creator<>() {
+ @NonNull
+ @Override
+ public MediaProjectionAppContent createFromParcel(@NonNull Parcel in) {
+ return new MediaProjectionAppContent(in);
+ }
+
+ @NonNull
+ @Override
+ public MediaProjectionAppContent[] newArray(int size) {
+ return new MediaProjectionAppContent[size];
+ }
+ };
+}
diff --git a/media/java/android/media/projection/MediaProjectionConfig.java b/media/java/android/media/projection/MediaProjectionConfig.java
index 598b534e81ca..cd674e9f2ad1 100644
--- a/media/java/android/media/projection/MediaProjectionConfig.java
+++ b/media/java/android/media/projection/MediaProjectionConfig.java
@@ -20,23 +20,56 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.os.Parcelable;
-import com.android.internal.util.AnnotationValidations;
+import com.android.media.projection.flags.Flags;
import java.lang.annotation.Retention;
+import java.util.Arrays;
+import java.util.Objects;
/**
* Configure the {@link MediaProjection} session requested from
* {@link MediaProjectionManager#createScreenCaptureIntent(MediaProjectionConfig)}.
+ * <p>
+ * This configuration should be used to provide the user with options for choosing the content to
+ * be shared with the requesting application.
*/
public final class MediaProjectionConfig implements Parcelable {
/**
+ * Bitmask for setting whether this configuration is for projecting the whole display.
+ */
+ @FlaggedApi(Flags.FLAG_APP_CONTENT_SHARING)
+ public static final int PROJECTION_SOURCE_DISPLAY = 1 << 1;
+
+ /**
+ * Bitmask for setting whether this configuration is for projecting the a custom region display.
+ *
+ * @hide
+ */
+ public static final int PROJECTION_SOURCE_DISPLAY_REGION = 1 << 2;
+
+ /**
+ * Bitmask for setting whether this configuration is for projecting the a single application.
+ */
+ @FlaggedApi(Flags.FLAG_APP_CONTENT_SHARING)
+ public static final int PROJECTION_SOURCE_APP = 1 << 3;
+
+ /**
+ * Bitmask for setting whether this configuration is for projecting the content provided by an
+ * application.
+ */
+ @FlaggedApi(com.android.media.projection.flags.Flags.FLAG_APP_CONTENT_SHARING)
+ public static final int PROJECTION_SOURCE_APP_CONTENT = 1 << 4;
+
+ /**
* The user, rather than the host app, determines which region of the display to capture.
*
* @hide
@@ -44,39 +77,109 @@ public final class MediaProjectionConfig implements Parcelable {
public static final int CAPTURE_REGION_USER_CHOICE = 0;
/**
+ * @hide
+ */
+ public static final int DEFAULT_PROJECTION_SOURCES =
+ PROJECTION_SOURCE_DISPLAY | PROJECTION_SOURCE_APP;
+
+ /**
* The host app specifies a particular display to capture.
*
* @hide
*/
public static final int CAPTURE_REGION_FIXED_DISPLAY = 1;
+ private static final int[] PROJECTION_SOURCES =
+ new int[]{PROJECTION_SOURCE_DISPLAY, PROJECTION_SOURCE_DISPLAY_REGION,
+ PROJECTION_SOURCE_APP,
+ PROJECTION_SOURCE_APP_CONTENT};
+
+ private static final String[] PROJECTION_SOURCES_STRING =
+ new String[]{"PROJECTION_SOURCE_DISPLAY", "PROJECTION_SOURCE_DISPLAY_REGION",
+ "PROJECTION_SOURCE_APP", "PROJECTION_SOURCE_APP_CONTENT"};
+
+ private static final int VALID_PROJECTION_SOURCES = createValidSourcesMask();
+
+ private final int mInitialSelection;
+
/** @hide */
@IntDef(prefix = "CAPTURE_REGION_", value = {CAPTURE_REGION_USER_CHOICE,
CAPTURE_REGION_FIXED_DISPLAY})
@Retention(SOURCE)
+ @Deprecated // Remove when FLAG_APP_CONTENT_SHARING is removed
public @interface CaptureRegion {
}
+ /** @hide */
+ @IntDef(flag = true, prefix = "PROJECTION_SOURCE_", value = {PROJECTION_SOURCE_DISPLAY,
+ PROJECTION_SOURCE_DISPLAY_REGION, PROJECTION_SOURCE_APP, PROJECTION_SOURCE_APP_CONTENT})
+ @Retention(SOURCE)
+ public @interface MediaProjectionSource {
+ }
+
/**
- * The particular display to capture. Only used when {@link #getRegionToCapture()} is
- * {@link #CAPTURE_REGION_FIXED_DISPLAY}; ignored otherwise.
+ * The particular display to capture. Only used when {@link #PROJECTION_SOURCE_DISPLAY} is set,
+ * ignored otherwise.
* <p>
* Only supports values of {@link android.view.Display#DEFAULT_DISPLAY}.
*/
@IntRange(from = DEFAULT_DISPLAY, to = DEFAULT_DISPLAY)
- private int mDisplayToCapture;
+ private final int mDisplayToCapture;
/**
* The region to capture. Defaults to the user's choice.
*/
@CaptureRegion
+ @Deprecated // Remove when FLAG_APP_CONTENT_SHARING is removed
private int mRegionToCapture;
/**
+ * The region to capture. Defaults to the user's choice.
+ */
+ @MediaProjectionSource
+ private final int mProjectionSources;
+
+ /**
+ * @see #getRequesterHint()
+ */
+ @Nullable
+ private final String mRequesterHint;
+
+ /**
* Customized instance, with region set to the provided value.
+ * @deprecated To be removed FLAG_APP_CONTENT_SHARING is removed
*/
+ @Deprecated // Remove when FLAG_APP_CONTENT_SHARING is removed
private MediaProjectionConfig(@CaptureRegion int captureRegion) {
+ if (Flags.appContentSharing()) {
+ throw new UnsupportedOperationException(
+ "Flag FLAG_APP_CONTENT_SHARING enabled. This method must not be called.");
+ }
mRegionToCapture = captureRegion;
+ mDisplayToCapture = DEFAULT_DISPLAY;
+
+ mRequesterHint = null;
+ mInitialSelection = -1;
+ mProjectionSources = -1;
+ }
+
+ /**
+ * Customized instance, with region set to the provided value.
+ */
+ private MediaProjectionConfig(@MediaProjectionSource int projectionSource,
+ @Nullable String requesterHint, int displayId, int initialSelection) {
+ if (!Flags.appContentSharing()) {
+ throw new UnsupportedOperationException(
+ "Flag FLAG_APP_CONTENT_SHARING disabled. This method must not be called");
+ }
+ if (projectionSource == 0) {
+ mProjectionSources = DEFAULT_PROJECTION_SOURCES;
+ } else {
+ mProjectionSources = projectionSource;
+ }
+ mRequesterHint = requesterHint;
+ mDisplayToCapture = displayId;
+ mInitialSelection = initialSelection;
}
/**
@@ -84,16 +187,17 @@ public final class MediaProjectionConfig implements Parcelable {
*/
@NonNull
public static MediaProjectionConfig createConfigForDefaultDisplay() {
- MediaProjectionConfig config = new MediaProjectionConfig(CAPTURE_REGION_FIXED_DISPLAY);
- config.mDisplayToCapture = DEFAULT_DISPLAY;
- return config;
+ if (Flags.appContentSharing()) {
+ return new Builder().setSourceEnabled(PROJECTION_SOURCE_DISPLAY, true).build();
+ } else {
+ return new MediaProjectionConfig(CAPTURE_REGION_FIXED_DISPLAY);
+ }
}
/**
* Returns an instance which allows the user to decide which region is captured. The consent
* dialog presents the user with all possible options. If the user selects display capture,
* then only the {@link android.view.Display#DEFAULT_DISPLAY} is supported.
- *
* <p>
* When passed in to
* {@link MediaProjectionManager#createScreenCaptureIntent(MediaProjectionConfig)}, the consent
@@ -103,13 +207,18 @@ public final class MediaProjectionConfig implements Parcelable {
*/
@NonNull
public static MediaProjectionConfig createConfigForUserChoice() {
- return new MediaProjectionConfig(CAPTURE_REGION_USER_CHOICE);
+ if (Flags.appContentSharing()) {
+ return new MediaProjectionConfig.Builder().build();
+ } else {
+ return new MediaProjectionConfig(CAPTURE_REGION_USER_CHOICE);
+ }
}
/**
* Returns string representation of the captured region.
*/
@NonNull
+ @Deprecated // Remove when FLAG_APP_CONTENT_SHARING is removed
private static String captureRegionToString(int value) {
return switch (value) {
case CAPTURE_REGION_USER_CHOICE -> "CAPTURE_REGION_USERS_CHOICE";
@@ -118,16 +227,42 @@ public final class MediaProjectionConfig implements Parcelable {
};
}
+ /**
+ * Returns string representation of the captured region.
+ */
+ @NonNull
+ private static String projectionSourceToString(int value) {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int i = 0; i < PROJECTION_SOURCES.length; i++) {
+ if ((value & PROJECTION_SOURCES[i]) > 0) {
+ stringBuilder.append(PROJECTION_SOURCES_STRING[i]);
+ stringBuilder.append(" ");
+ value &= ~PROJECTION_SOURCES[i];
+ }
+ }
+ if (value > 0) {
+ stringBuilder.append("Unknown projection sources: ");
+ stringBuilder.append(Integer.toHexString(value));
+ }
+ return stringBuilder.toString();
+ }
+
@Override
public String toString() {
- return "MediaProjectionConfig { " + "displayToCapture = " + mDisplayToCapture + ", "
- + "regionToCapture = " + captureRegionToString(mRegionToCapture) + " }";
+ if (Flags.appContentSharing()) {
+ return ("MediaProjectionConfig{mInitialSelection=%d, mDisplayToCapture=%d, "
+ + "mProjectionSource=%s, mRequesterHint='%s'}").formatted(mInitialSelection,
+ mDisplayToCapture, projectionSourceToString(mProjectionSources),
+ mRequesterHint);
+ } else {
+ return "MediaProjectionConfig { " + "displayToCapture = " + mDisplayToCapture + ", "
+ + "regionToCapture = " + captureRegionToString(mRegionToCapture) + " }";
+ }
}
-
/**
- * The particular display to capture. Only used when {@link #getRegionToCapture()} is
- * {@link #CAPTURE_REGION_FIXED_DISPLAY}; ignored otherwise.
+ * The particular display to capture. Only used when {@link #PROJECTION_SOURCE_DISPLAY} is
+ * set; ignored otherwise.
* <p>
* Only supports values of {@link android.view.Display#DEFAULT_DISPLAY}.
*
@@ -146,27 +281,57 @@ public final class MediaProjectionConfig implements Parcelable {
return mRegionToCapture;
}
+ /**
+ * A bitmask representing of requested projection sources.
+ * <p>
+ * The system supports different kind of media projection session. Although the user is
+ * picking the target content, the requesting application can configure the choices displayed
+ * to the user.
+ */
+ @FlaggedApi(Flags.FLAG_APP_CONTENT_SHARING)
+ public @MediaProjectionSource int getProjectionSources() {
+ return mProjectionSources;
+ }
+
@Override
public boolean equals(@Nullable Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MediaProjectionConfig that = (MediaProjectionConfig) o;
- return mDisplayToCapture == that.mDisplayToCapture
- && mRegionToCapture == that.mRegionToCapture;
+ if (Flags.appContentSharing()) {
+ return mDisplayToCapture == that.mDisplayToCapture
+ && mProjectionSources == that.mProjectionSources
+ && mInitialSelection == that.mInitialSelection
+ && Objects.equals(mRequesterHint, that.mRequesterHint);
+ } else {
+ return mDisplayToCapture == that.mDisplayToCapture
+ && mRegionToCapture == that.mRegionToCapture;
+ }
}
@Override
public int hashCode() {
int _hash = 1;
- _hash = 31 * _hash + mDisplayToCapture;
- _hash = 31 * _hash + mRegionToCapture;
+ if (Flags.appContentSharing()) {
+ return Objects.hash(mDisplayToCapture, mProjectionSources, mInitialSelection,
+ mRequesterHint);
+ } else {
+ _hash = 31 * _hash + mDisplayToCapture;
+ _hash = 31 * _hash + mRegionToCapture;
+ }
return _hash;
}
@Override
public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
dest.writeInt(mDisplayToCapture);
- dest.writeInt(mRegionToCapture);
+ if (Flags.appContentSharing()) {
+ dest.writeInt(mProjectionSources);
+ dest.writeString(mRequesterHint);
+ dest.writeInt(mInitialSelection);
+ } else {
+ dest.writeInt(mRegionToCapture);
+ }
}
@Override
@@ -176,12 +341,17 @@ public final class MediaProjectionConfig implements Parcelable {
/** @hide */
/* package-private */ MediaProjectionConfig(@NonNull android.os.Parcel in) {
- int displayToCapture = in.readInt();
- int regionToCapture = in.readInt();
-
- mDisplayToCapture = displayToCapture;
- mRegionToCapture = regionToCapture;
- AnnotationValidations.validate(CaptureRegion.class, null, mRegionToCapture);
+ mDisplayToCapture = in.readInt();
+ if (Flags.appContentSharing()) {
+ mProjectionSources = in.readInt();
+ mRequesterHint = in.readString();
+ mInitialSelection = in.readInt();
+ } else {
+ mRegionToCapture = in.readInt();
+ mProjectionSources = -1;
+ mRequesterHint = null;
+ mInitialSelection = -1;
+ }
}
public static final @NonNull Parcelable.Creator<MediaProjectionConfig> CREATOR =
@@ -196,4 +366,138 @@ public final class MediaProjectionConfig implements Parcelable {
return new MediaProjectionConfig(in);
}
};
+
+ /**
+ * Returns true if the provided source should be enabled.
+ *
+ * @param projectionSource projection source integer to check for. The parameter can also be a
+ * bitmask of multiple sources.
+ */
+ @FlaggedApi(Flags.FLAG_APP_CONTENT_SHARING)
+ public boolean isSourceEnabled(@MediaProjectionSource int projectionSource) {
+ return (mProjectionSources & projectionSource) > 0;
+ }
+
+ /**
+ * Returns a bit mask of one, and only one, of the projection type flag.
+ */
+ @FlaggedApi(Flags.FLAG_APP_CONTENT_SHARING)
+ @MediaProjectionSource
+ public int getInitiallySelectedSource() {
+ return mInitialSelection;
+ }
+
+ /**
+ * A hint set by the requesting app indicating who the requester of this {@link MediaProjection}
+ * session is.
+ * <p>
+ * The UI component prompting the user for the permission to start the session can use
+ * this hint to provide more information about the origin of the request (e.g. a browser
+ * tab title, a meeting id if sharing to a video conferencing app, a player name if
+ * sharing the screen within a game).
+ *
+ * @return the hint to be displayed if set, null otherwise.
+ */
+ @FlaggedApi(Flags.FLAG_APP_CONTENT_SHARING)
+ @Nullable
+ public CharSequence getRequesterHint() {
+ return mRequesterHint;
+ }
+
+ private static int createValidSourcesMask() {
+ int validSources = 0;
+ for (int projectionSource : PROJECTION_SOURCES) {
+ validSources |= projectionSource;
+ }
+ return validSources;
+ }
+
+ @FlaggedApi(Flags.FLAG_APP_CONTENT_SHARING)
+ public static final class Builder {
+ private int mOptions = 0;
+ private String mRequesterHint = null;
+
+ @MediaProjectionSource
+ private int mInitialSelection;
+
+ public Builder() {
+ if (!Flags.appContentSharing()) {
+ throw new UnsupportedOperationException("Flag FLAG_APP_CONTENT_SHARING disabled");
+ }
+ }
+
+ /**
+ * Indicates which projection source the UI component should display to the user
+ * first. Calling this method without enabling the respective choice will have no effect.
+ *
+ * @return instance of this {@link Builder}.
+ * @see #setSourceEnabled(int, boolean)
+ */
+ @NonNull
+ public Builder setInitiallySelectedSource(@MediaProjectionSource int projectionSource) {
+ for (int source : PROJECTION_SOURCES) {
+ if (projectionSource == source) {
+ mInitialSelection = projectionSource;
+ return this;
+ }
+ }
+ throw new IllegalArgumentException(
+ ("projectionSource is no a valid projection source. projectionSource must be "
+ + "one of %s but was %s")
+ .formatted(Arrays.toString(PROJECTION_SOURCES_STRING),
+ projectionSourceToString(projectionSource)));
+ }
+
+ /**
+ * Let the requesting app indicate who the requester of this {@link MediaProjection}
+ * session is..
+ * <p>
+ * The UI component prompting the user for the permission to start the session can use
+ * this hint to provide more information about the origin of the request (e.g. a browser
+ * tab title, a meeting id if sharing to a video conferencing app, a player name if
+ * sharing the screen within a game).
+ * <p>
+ * Note that setting this won't hide or change the name of the application
+ * requesting the session.
+ *
+ * @return instance of this {@link Builder}.
+ */
+ @NonNull
+ public Builder setRequesterHint(@Nullable String requesterHint) {
+ mRequesterHint = requesterHint;
+ return this;
+ }
+
+ /**
+ * Set whether the UI component requesting the user permission to share their screen
+ * should display an option to share the specified source
+ *
+ * @param source the projection source to enable or disable
+ * @param enabled true to enable the source, false otherwise
+ * @return this instance for chaining.
+ * @throws IllegalArgumentException if the source is not one of the valid sources.
+ */
+ @NonNull
+ @SuppressLint("MissingGetterMatchingBuilder") // isSourceEnabled is defined
+ public Builder setSourceEnabled(@MediaProjectionSource int source, boolean enabled) {
+ if ((source & VALID_PROJECTION_SOURCES) == 0) {
+ throw new IllegalArgumentException(
+ ("source is no a valid projection source. source must be "
+ + "any of %s but was %s")
+ .formatted(Arrays.toString(PROJECTION_SOURCES_STRING),
+ projectionSourceToString(source)));
+ }
+ mOptions = enabled ? mOptions | source : mOptions & ~source;
+ return this;
+ }
+
+ /**
+ * Builds a new immutable instance of {@link MediaProjectionConfig}
+ */
+ @NonNull
+ public MediaProjectionConfig build() {
+ return new MediaProjectionConfig(mOptions, mRequesterHint, DEFAULT_DISPLAY,
+ mInitialSelection);
+ }
+ }
}
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 9036bf385d96..4a5392d3c0c3 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -29,6 +29,7 @@ import android.compat.annotation.Overridable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.hardware.display.VirtualDisplay;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
@@ -78,9 +79,12 @@ public final class MediaProjectionManager {
private static final String TAG = "MediaProjectionManager";
/**
- * This change id ensures that users are presented with a choice of capturing a single app
- * or the entire screen when initiating a MediaProjection session, overriding the usage of
- * MediaProjectionConfig#createConfigForDefaultDisplay.
+ * If enabled, this change id ensures that users are presented with a choice of capturing a
+ * single app and the entire screen when initiating a MediaProjection session, overriding the
+ * usage of MediaProjectionConfig#createConfigForDefaultDisplay.
+ * <p>
+ *
+ * <a href=" https://developer.android.com/guide/practices/device-compatibility-mode#override_disable_media_projection_single_app_option">More info</a>
*
* @hide
*/
diff --git a/media/java/android/media/projection/TEST_MAPPING b/media/java/android/media/projection/TEST_MAPPING
index ea62287b7411..62e776b822d2 100644
--- a/media/java/android/media/projection/TEST_MAPPING
+++ b/media/java/android/media/projection/TEST_MAPPING
@@ -4,4 +4,4 @@
"path": "frameworks/base/services/core/java/com/android/server/media/projection"
}
]
-} \ No newline at end of file
+}
diff --git a/media/java/android/media/quality/PictureProfile.java b/media/java/android/media/quality/PictureProfile.java
index 8a585efe032c..3bccd89c91c3 100644
--- a/media/java/android/media/quality/PictureProfile.java
+++ b/media/java/android/media/quality/PictureProfile.java
@@ -114,6 +114,18 @@ public final class PictureProfile implements Parcelable {
*/
public static final int ERROR_NOT_ALLOWLISTED = 4;
+ /**
+ * SDR status.
+ * @hide
+ */
+ public static final String STATUS_SDR = "SDR";
+
+ /**
+ * HDR status.
+ * @hide
+ */
+ public static final String STATUS_HDR = "HDR";
+
private PictureProfile(@NonNull Parcel in) {
mId = in.readString();
diff --git a/media/java/android/media/quality/aidl/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/aidl/android/media/quality/IMediaQualityManager.aidl
index b5afa6afa5e0..6ac1656b77aa 100644
--- a/media/java/android/media/quality/aidl/android/media/quality/IMediaQualityManager.aidl
+++ b/media/java/android/media/quality/aidl/android/media/quality/IMediaQualityManager.aidl
@@ -47,7 +47,7 @@ interface IMediaQualityManager {
void setPictureProfileAllowList(in List<String> packages, int userId);
List<PictureProfileHandle> getPictureProfileHandle(in String[] id, int userId);
- SoundProfile createSoundProfile(in SoundProfile pp, int userId);
+ void createSoundProfile(in SoundProfile pp, int userId);
void updateSoundProfile(in String id, in SoundProfile pp, int userId);
void removeSoundProfile(in String id, int userId);
boolean setDefaultSoundProfile(in String id, int userId);
diff --git a/media/tests/projection/Android.bp b/media/tests/projection/Android.bp
index 0b02d3cb4250..0b4b7dbbca1f 100644
--- a/media/tests/projection/Android.bp
+++ b/media/tests/projection/Android.bp
@@ -26,6 +26,7 @@ android_test {
"androidx.test.runner",
"androidx.test.rules",
"androidx.test.ext.junit",
+ "flag-junit",
"frameworks-base-testutils",
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
diff --git a/media/tests/projection/src/android/media/projection/MediaProjectionAppContentTest.java b/media/tests/projection/src/android/media/projection/MediaProjectionAppContentTest.java
new file mode 100644
index 000000000000..7e167c63a2a2
--- /dev/null
+++ b/media/tests/projection/src/android/media/projection/MediaProjectionAppContentTest.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Bitmap;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionAppContentTest {
+
+ @Test
+ public void testConstructorAndGetters() {
+ // Create a mock Bitmap
+ Bitmap mockBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+
+ // Create a MediaProjectionAppContent object
+ MediaProjectionAppContent content = new MediaProjectionAppContent(mockBitmap, "Test Title",
+ 123);
+
+ // Verify the values using getters
+ assertThat(content.getTitle()).isEqualTo("Test Title");
+ assertThat(content.getId()).isEqualTo(123);
+ // Compare bitmap configurations and dimensions
+ assertThat(content.getThumbnail().getConfig()).isEqualTo(mockBitmap.getConfig());
+ assertThat(content.getThumbnail().getWidth()).isEqualTo(mockBitmap.getWidth());
+ assertThat(content.getThumbnail().getHeight()).isEqualTo(mockBitmap.getHeight());
+ }
+
+ @Test
+ public void testParcelable() {
+ // Create a mock Bitmap
+ Bitmap mockBitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+
+ // Create a MediaProjectionAppContent object
+ MediaProjectionAppContent content = new MediaProjectionAppContent(mockBitmap, "Test Title",
+ 123);
+
+ // Parcel and unparcel the object
+ Parcel parcel = Parcel.obtain();
+ content.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ MediaProjectionAppContent unparceledContent =
+ MediaProjectionAppContent.CREATOR.createFromParcel(parcel);
+
+ // Verify the values of the unparceled object
+ assertThat(unparceledContent.getTitle()).isEqualTo("Test Title");
+ assertThat(unparceledContent.getId()).isEqualTo(123);
+ // Compare bitmap configurations and dimensions
+ assertThat(unparceledContent.getThumbnail().getConfig()).isEqualTo(mockBitmap.getConfig());
+ assertThat(unparceledContent.getThumbnail().getWidth()).isEqualTo(mockBitmap.getWidth());
+ assertThat(unparceledContent.getThumbnail().getHeight()).isEqualTo(mockBitmap.getHeight());
+
+ parcel.recycle();
+ }
+
+ @Test
+ public void testCreatorNewArray() {
+ // Create a new array using the CREATOR
+ MediaProjectionAppContent[] contentArray = MediaProjectionAppContent.CREATOR.newArray(5);
+
+ // Verify that the array is not null and has the correct size
+ assertThat(contentArray).isNotNull();
+ assertThat(contentArray).hasLength(5);
+ }
+}
diff --git a/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java b/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java
index 2820606958b7..bc0eae1a3ec7 100644
--- a/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java
+++ b/media/tests/projection/src/android/media/projection/MediaProjectionConfigTest.java
@@ -18,22 +18,31 @@ package android.media.projection;
import static android.media.projection.MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY;
import static android.media.projection.MediaProjectionConfig.CAPTURE_REGION_USER_CHOICE;
+import static android.media.projection.MediaProjectionConfig.PROJECTION_SOURCE_DISPLAY;
+import static android.media.projection.MediaProjectionConfig.DEFAULT_PROJECTION_SOURCES;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.google.common.truth.Truth.assertThat;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.media.projection.flags.Flags;
+
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Tests for the {@link MediaProjectionConfig} class.
- *
+ * <p>
* Build/Install/Run:
* atest MediaProjectionTests:MediaProjectionConfigTest
*/
@@ -41,6 +50,11 @@ import org.junit.runner.RunWith;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class MediaProjectionConfigTest {
+
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
private static final MediaProjectionConfig DISPLAY_CONFIG =
MediaProjectionConfig.createConfigForDefaultDisplay();
private static final MediaProjectionConfig USERS_CHOICE_CONFIG =
@@ -57,17 +71,33 @@ public class MediaProjectionConfigTest {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_CONTENT_SHARING)
public void testCreateDisplayConfig() {
assertThat(DISPLAY_CONFIG.getRegionToCapture()).isEqualTo(CAPTURE_REGION_FIXED_DISPLAY);
assertThat(DISPLAY_CONFIG.getDisplayToCapture()).isEqualTo(DEFAULT_DISPLAY);
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_APP_CONTENT_SHARING)
public void testCreateUsersChoiceConfig() {
assertThat(USERS_CHOICE_CONFIG.getRegionToCapture()).isEqualTo(CAPTURE_REGION_USER_CHOICE);
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_APP_CONTENT_SHARING)
+ public void testDefaultProjectionSources() {
+ assertThat(USERS_CHOICE_CONFIG.getProjectionSources())
+ .isEqualTo(DEFAULT_PROJECTION_SOURCES);
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_APP_CONTENT_SHARING)
+ public void testCreateDisplayConfigProjectionSource() {
+ assertThat(DISPLAY_CONFIG.getProjectionSources()).isEqualTo(PROJECTION_SOURCE_DISPLAY);
+ assertThat(DISPLAY_CONFIG.getDisplayToCapture()).isEqualTo(DEFAULT_DISPLAY);
+ }
+
+ @Test
public void testEquals() {
assertThat(MediaProjectionConfig.createConfigForUserChoice()).isEqualTo(
USERS_CHOICE_CONFIG);
diff --git a/packages/SettingsLib/SettingsSpinner/res/drawable-v36/settings_expressive_spinner_dropdown_background.xml b/packages/SettingsLib/SettingsSpinner/res/drawable-v36/settings_expressive_spinner_dropdown_background.xml
new file mode 100644
index 000000000000..f29f3ae79fa6
--- /dev/null
+++ b/packages/SettingsLib/SettingsSpinner/res/drawable-v36/settings_expressive_spinner_dropdown_background.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <!-- Highlight the selected item -->
+ <item android:state_activated="true" android:drawable="@drawable/settings_expressive_spinner_dropdown_item_selected"/>
+</selector>
diff --git a/packages/SettingsLib/SettingsSpinner/res/drawable-v36/settings_expressive_spinner_dropdown_item_selected.xml b/packages/SettingsLib/SettingsSpinner/res/drawable-v36/settings_expressive_spinner_dropdown_item_selected.xml
new file mode 100644
index 000000000000..5da3f7172582
--- /dev/null
+++ b/packages/SettingsLib/SettingsSpinner/res/drawable-v36/settings_expressive_spinner_dropdown_item_selected.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:colorControlHighlight">
+ <item>
+ <shape android:shape="rectangle">
+ <solid
+ android:color="@color/settingslib_materialColorPrimaryContainer" />
+ <corners
+ android:radius="@dimen/settingslib_expressive_radius_large2" />
+ </shape>
+ </item>
+</ripple>
diff --git a/packages/SettingsLib/SettingsSpinner/res/layout-v33/settings_spinner_view.xml b/packages/SettingsLib/SettingsSpinner/res/layout-v33/settings_spinner_view.xml
index 1d0c9b941881..3c379bf0162d 100644
--- a/packages/SettingsLib/SettingsSpinner/res/layout-v33/settings_spinner_view.xml
+++ b/packages/SettingsLib/SettingsSpinner/res/layout-v33/settings_spinner_view.xml
@@ -18,6 +18,8 @@
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/text1"
+ android:layout_centerVertical="true"
+ android:gravity="center_vertical"
style="@style/SettingsSpinnerTitleBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
diff --git a/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_preference_full.xml b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_preference_full.xml
new file mode 100644
index 000000000000..6d1057c8780b
--- /dev/null
+++ b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_preference_full.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:filterTouchesWhenObscured="true">
+ <Spinner
+ android:id="@+id/spinner"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"/>
+</RelativeLayout>
diff --git a/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_preference_full_outlined.xml b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_preference_full_outlined.xml
new file mode 100644
index 000000000000..217d1431cd18
--- /dev/null
+++ b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_preference_full_outlined.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:filterTouchesWhenObscured="true">
+
+ <Spinner
+ android:id="@+id/spinner"
+ style="@style/SettingslibSpinnerStyle.Expressive.Outlined"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"/>
+</RelativeLayout>
diff --git a/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_preference_outlined.xml b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_preference_outlined.xml
new file mode 100644
index 000000000000..3aefb887cedb
--- /dev/null
+++ b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_preference_outlined.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:filterTouchesWhenObscured="true">
+
+ <Spinner
+ android:id="@+id/spinner"
+ style="@style/SettingslibSpinnerStyle.Expressive.Outlined"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"/>
+</RelativeLayout>
diff --git a/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_view_full.xml b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_view_full.xml
new file mode 100644
index 000000000000..d3832f786ccb
--- /dev/null
+++ b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_view_full.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/text1"
+ android:layout_centerVertical="true"
+ android:gravity="center_vertical"
+ style="@style/SettingsSpinnerTitleBar.Expressive.Large"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:filterTouchesWhenObscured="true"/>
diff --git a/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_view_full_outlined.xml b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_view_full_outlined.xml
new file mode 100644
index 000000000000..2c172e955a09
--- /dev/null
+++ b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_view_full_outlined.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/text1"
+ android:layout_centerVertical="true"
+ android:gravity="center_vertical"
+ style="@style/SettingsSpinnerTitleBar.Expressive.Large.Outlined"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:filterTouchesWhenObscured="true"/>
diff --git a/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_view_large.xml b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_view_large.xml
new file mode 100644
index 000000000000..3e7f0fa7ca4f
--- /dev/null
+++ b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_view_large.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/text1"
+ android:layout_centerVertical="true"
+ android:gravity="center_vertical"
+ style="@style/SettingsSpinnerTitleBar.Expressive.Large"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:filterTouchesWhenObscured="true"/>
diff --git a/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_view_large_outlined.xml b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_view_large_outlined.xml
new file mode 100644
index 000000000000..6601c8cd97a5
--- /dev/null
+++ b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressive_spinner_view_large_outlined.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/text1"
+ android:layout_centerVertical="true"
+ android:gravity="center_vertical"
+ style="@style/SettingsSpinnerTitleBar.Expressive.Large.Outlined"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:filterTouchesWhenObscured="true"/>
diff --git a/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressvie_spinner_dropdown_view.xml b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressvie_spinner_dropdown_view.xml
new file mode 100644
index 000000000000..acf2a0dd5858
--- /dev/null
+++ b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressvie_spinner_dropdown_view.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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"
+ style="@style/SettingsSpinnerDropdown.Expressive">
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="@dimen/settingslib_expressive_space_small3"
+ android:layout_height="@dimen/settingslib_expressive_space_small3"
+ android:importantForAccessibility="no"
+ android:src="@drawable/settingslib_expressive_icon_check"
+ android:tint="@color/settingslib_spinner_dropdown_color"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="@dimen/settingslib_expressive_space_extrasmall4"
+ android:scaleType="centerInside"/>
+
+ <TextView
+ android:id="@android:id/text1"
+ style="@style/SettingsSpinnerDropdownText"
+ android:gravity="center_vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:filterTouchesWhenObscured="true"/>
+</LinearLayout>
diff --git a/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressvie_spinner_view.xml b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressvie_spinner_view.xml
new file mode 100644
index 000000000000..e300099ee298
--- /dev/null
+++ b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressvie_spinner_view.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/text1"
+ android:layout_centerVertical="true"
+ android:gravity="center_vertical"
+ style="@style/SettingsSpinnerTitleBar.Expressive"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:filterTouchesWhenObscured="true"/>
diff --git a/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressvie_spinner_view_outlined.xml b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressvie_spinner_view_outlined.xml
new file mode 100644
index 000000000000..73e254e9bc15
--- /dev/null
+++ b/packages/SettingsLib/SettingsSpinner/res/layout-v36/settings_expressvie_spinner_view_outlined.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/text1"
+ android:layout_centerVertical="true"
+ android:gravity="center_vertical"
+ style="@style/SettingsSpinnerTitleBar.Expressive.Outlined"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:filterTouchesWhenObscured="true"/>
diff --git a/packages/SettingsLib/SettingsSpinner/res/values-v33/styles.xml b/packages/SettingsLib/SettingsSpinner/res/values-v33/styles.xml
index 6e26ae180685..2d720d210def 100644
--- a/packages/SettingsLib/SettingsSpinner/res/values-v33/styles.xml
+++ b/packages/SettingsLib/SettingsSpinner/res/values-v33/styles.xml
@@ -27,6 +27,7 @@
<item name="android:paddingEnd">36dp</item>
<item name="android:paddingTop">@dimen/spinner_padding_top_or_bottom</item>
<item name="android:paddingBottom">@dimen/spinner_padding_top_or_bottom</item>
+ <item name="android:filterTouchesWhenObscured">true</item>
</style>
<style name="SettingsSpinnerDropdown">
@@ -40,5 +41,6 @@
<item name="android:paddingEnd">36dp</item>
<item name="android:paddingTop">@dimen/spinner_padding_top_or_bottom</item>
<item name="android:paddingBottom">@dimen/spinner_padding_top_or_bottom</item>
+ <item name="android:filterTouchesWhenObscured">true</item>
</style>
</resources>
diff --git a/packages/SettingsLib/SettingsSpinner/res/values-v36/attr.xml b/packages/SettingsLib/SettingsSpinner/res/values-v36/attr.xml
new file mode 100644
index 000000000000..154149acf26d
--- /dev/null
+++ b/packages/SettingsLib/SettingsSpinner/res/values-v36/attr.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2025 The Android Open Source Project
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <attr name="SettingsSpinnerPreferenceStyle" format="reference"/>
+ <declare-styleable name="SettingsSpinnerPreference">
+ <attr name="style" format="enum">
+ <enum name="normal" value="0"/>
+ <enum name="large" value="1"/>
+ <enum name="full" value="2"/>
+ <enum name="outlined" value="3"/>
+ <enum name="large_outlined" value="4"/>
+ <enum name="full_outlined" value="5"/>
+ </attr>
+ </declare-styleable>
+</resources>
diff --git a/packages/SettingsLib/SettingsSpinner/res/values-v36/styles.xml b/packages/SettingsLib/SettingsSpinner/res/values-v36/styles.xml
new file mode 100644
index 000000000000..2cb4518af287
--- /dev/null
+++ b/packages/SettingsLib/SettingsSpinner/res/values-v36/styles.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources>
+ <style name="SettingsSpinnerTitleBar.Expressive">
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.LabelLarge</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnSecondaryContainer</item>
+ <item name="android:maxLines">1</item>
+ <item name="android:ellipsize">marquee</item>
+ <item name="android:minHeight">@dimen/settingslib_expressive_space_medium3</item>
+ <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small1</item>
+ <item name="android:paddingVertical">@dimen/settingslib_expressive_space_extrasmall5</item>
+ </style>
+
+ <style name="SettingsSpinnerTitleBar.Expressive.Large">
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleMedium</item>
+ <item name="android:minHeight">@dimen/settingslib_expressive_space_medium5</item>
+ <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small4</item>
+ <item name="android:paddingVertical">@dimen/settingslib_expressive_space_small1</item>
+ </style>
+
+ <style name="SettingsSpinnerTitleBar.Expressive.Outlined">
+ <item name="android:textColor">@color/settingslib_materialColorPrimary</item>
+ </style>
+
+ <style name="SettingsSpinnerTitleBar.Expressive.Large.Outlined">
+ <item name="android:textColor">@color/settingslib_materialColorPrimary</item>
+ </style>
+
+ <style name="SettingsSpinnerDropdown.Expressive">
+ <item name="android:background">@drawable/settings_expressive_spinner_dropdown_background</item>
+ <item name="android:minHeight">@dimen/spinner_dropdown_height</item>
+ <item name="android:paddingStart">@dimen/settingslib_expressive_space_extrasmall4</item>
+ <item name="android:paddingEnd">@dimen/settingslib_expressive_space_extrasmall4</item>
+ <item name="android:paddingTop">@dimen/settingslib_expressive_space_extrasmall7</item>
+ <item name="android:paddingBottom">@dimen/settingslib_expressive_space_extrasmall7</item>
+ </style>
+
+ <style name="SettingsSpinnerDropdownText">
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.LabelLarge</item>
+ <item name="android:textColor">@color/settingslib_spinner_dropdown_color</item>
+ <item name="android:maxLines">1</item>
+ <item name="android:ellipsize">marquee</item>
+ </style>
+</resources>
diff --git a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerAdapter.java b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerAdapter.java
index f33cacd36c6d..2f9f7038f6f7 100644
--- a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerAdapter.java
+++ b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerAdapter.java
@@ -22,7 +22,13 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import com.android.settingslib.widget.SettingsSpinnerPreference.Style;
import com.android.settingslib.widget.spinner.R;
+
/**
* An ArrayAdapter which was used by Spinner with settings style.
* @param <T> the data type to be loaded.
@@ -30,8 +36,13 @@ import com.android.settingslib.widget.spinner.R;
public class SettingsSpinnerAdapter<T> extends ArrayAdapter<T> {
private static final int DEFAULT_RESOURCE = R.layout.settings_spinner_view;
- private static final int DFAULT_DROPDOWN_RESOURCE = R.layout.settings_spinner_dropdown_view;
+ private static final int DEFAULT_DROPDOWN_RESOURCE = R.layout.settings_spinner_dropdown_view;
+ private static final int DEFAULT_EXPRESSIVE_RESOURCE =
+ R.layout.settings_expressvie_spinner_view;
+ private static final int DEFAULT_EXPRESSIVE_DROPDOWN_RESOURCE =
+ R.layout.settings_expressvie_spinner_dropdown_view;
private final LayoutInflater mDefaultInflater;
+ private int mSelectedPosition = -1;
/**
* Constructs a new SettingsSpinnerAdapter with the given context.
@@ -41,17 +52,74 @@ public class SettingsSpinnerAdapter<T> extends ArrayAdapter<T> {
* access the current theme, resources, etc.
*/
public SettingsSpinnerAdapter(Context context) {
- super(context, getDefaultResource());
+ super(context, getDefaultResource(context, Style.NORMAL));
+
+ setDropDownViewResource(getDropdownResource(context));
+ mDefaultInflater = LayoutInflater.from(context);
+ }
+
+ @Override
+ public View getDropDownView(
+ int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+ View view;
+ if (convertView == null) {
+ view =
+ mDefaultInflater.inflate(
+ getDropdownResource(getContext()), parent, false /* attachToRoot */);
+ } else {
+ view = convertView;
+ }
+ TextView textView = view.findViewById(android.R.id.text1);
+ ImageView iconView = view.findViewById(android.R.id.icon);
+ iconView.setVisibility((position == mSelectedPosition) ? View.VISIBLE : View.GONE);
+ String item = (String) getItem(position);
+ textView.setText(item);
+ return view;
+ }
- setDropDownViewResource(getDropdownResource());
+ public void setSelectedPosition(int pos) {
+ mSelectedPosition = pos;
+ }
+
+ public SettingsSpinnerAdapter(Context context, SettingsSpinnerPreference.Style style) {
+ super(context, getDefaultResource(context, style));
+
+ setDropDownViewResource(getDropdownResource(context));
mDefaultInflater = LayoutInflater.from(context);
}
+ private static int getDefaultResourceWithStyle(Style style) {
+ switch (style) {
+ case NORMAL -> {
+ return DEFAULT_EXPRESSIVE_RESOURCE;
+ }
+ case LARGE -> {
+ return R.layout.settings_expressive_spinner_view_large;
+ }
+ case FULL_WIDTH -> {
+ return R.layout.settings_expressive_spinner_view_full;
+ }
+ case OUTLINED -> {
+ return R.layout.settings_expressvie_spinner_view_outlined;
+ }
+ case LARGE_OUTLINED -> {
+ return R.layout.settings_expressive_spinner_view_large_outlined;
+ }
+ case FULL_OUTLINED -> {
+ return R.layout.settings_expressive_spinner_view_full_outlined;
+ }
+ default -> {
+ return DEFAULT_RESOURCE;
+ }
+ }
+ }
+
/**
* In overridded {@link #getView(int, View, ViewGroup)}, use this method to get default view.
*/
public View getDefaultView(int position, View convertView, ViewGroup parent) {
- return mDefaultInflater.inflate(getDefaultResource(), parent, false /* attachToRoot */);
+ return mDefaultInflater.inflate(
+ getDefaultResource(getContext(), Style.NORMAL), parent, false /* attachToRoot */);
}
/**
@@ -59,15 +127,21 @@ public class SettingsSpinnerAdapter<T> extends ArrayAdapter<T> {
* drop down view.
*/
public View getDefaultDropDownView(int position, View convertView, ViewGroup parent) {
- return mDefaultInflater.inflate(getDropdownResource(), parent, false /* attachToRoot */);
+ return mDefaultInflater.inflate(
+ getDropdownResource(getContext()), parent, false /* attachToRoot */);
}
- private static int getDefaultResource() {
+ private static int getDefaultResource(Context context, Style style) {
+ int resId = SettingsThemeHelper.isExpressiveTheme(context)
+ ? getDefaultResourceWithStyle(style) : DEFAULT_DROPDOWN_RESOURCE;
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
- ? DEFAULT_RESOURCE : android.R.layout.simple_spinner_dropdown_item;
+ ? resId : android.R.layout.simple_spinner_dropdown_item;
}
- private static int getDropdownResource() {
+
+ private static int getDropdownResource(Context context) {
+ int resId = SettingsThemeHelper.isExpressiveTheme(context)
+ ? DEFAULT_EXPRESSIVE_DROPDOWN_RESOURCE : DEFAULT_DROPDOWN_RESOURCE;
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
- ? DFAULT_DROPDOWN_RESOURCE : android.R.layout.simple_spinner_dropdown_item;
+ ? resId : android.R.layout.simple_spinner_dropdown_item;
}
}
diff --git a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java
index 1170f1e7c695..b357369155b6 100644
--- a/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java
+++ b/packages/SettingsLib/SettingsSpinner/src/com/android/settingslib/widget/SettingsSpinnerPreference.java
@@ -17,12 +17,15 @@
package com.android.settingslib.widget;
import android.content.Context;
+import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.widget.AdapterView;
import android.widget.Spinner;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.Preference.OnPreferenceClickListener;
import androidx.preference.PreferenceViewHolder;
@@ -44,29 +47,28 @@ public class SettingsSpinnerPreference extends Preference
/**
* Perform inflation from XML and apply a class-specific base style.
*
- * @param context The {@link Context} this is associated with, through which it can
- * access the current theme, resources, {@link SharedPreferences}, etc.
- * @param attrs The attributes of the XML tag that is inflating the preference
+ * @param context The {@link Context} this is associated with, through which it can access the
+ * current theme, resources, {@link SharedPreferences}, etc.
+ * @param attrs The attributes of the XML tag that is inflating the preference
* @param defStyle An attribute in the current theme that contains a reference to a style
- * resource that supplies default values for the view. Can be 0 to not
- * look for defaults.
+ * resource that supplies default values for the view. Can be 0 to not look for defaults.
*/
public SettingsSpinnerPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
- setLayoutResource(R.layout.settings_spinner_preference);
+ initAttributes(context, attrs, defStyle);
setOnPreferenceClickListener(this);
}
/**
* Perform inflation from XML and apply a class-specific base style.
*
- * @param context The {@link Context} this is associated with, through which it can
- * access the current theme, resources, {@link SharedPreferences}, etc.
- * @param attrs The attributes of the XML tag that is inflating the preference
+ * @param context The {@link Context} this is associated with, through which it can access the
+ * current theme, resources, {@link SharedPreferences}, etc.
+ * @param attrs The attributes of the XML tag that is inflating the preference
*/
- public SettingsSpinnerPreference(Context context, AttributeSet attrs) {
+ public SettingsSpinnerPreference(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
- setLayoutResource(R.layout.settings_spinner_preference);
+ initAttributes(context, attrs, 0);
setOnPreferenceClickListener(this);
}
@@ -75,8 +77,36 @@ public class SettingsSpinnerPreference extends Preference
*
* @param context The Context this is associated with.
*/
- public SettingsSpinnerPreference(Context context) {
+ public SettingsSpinnerPreference(@NonNull Context context) {
this(context, null);
+ initAttributes(context, null, 0);
+ }
+
+ public enum Style {
+ NORMAL,
+ LARGE,
+ FULL_WIDTH,
+ OUTLINED,
+ LARGE_OUTLINED,
+ FULL_OUTLINED,
+ }
+
+ private void initAttributes(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ int layoutRes = R.layout.settings_spinner_preference;
+ try (TypedArray a =
+ context.obtainStyledAttributes(
+ attrs, R.styleable.SettingsSpinnerPreference, defStyleAttr, 0)) {
+ int style = a.getInteger(R.styleable.SettingsSpinnerPreference_style, 0);
+ switch (style) {
+ case 2 -> layoutRes = R.layout.settings_expressive_spinner_preference_full;
+ case 3 -> layoutRes = R.layout.settings_expressive_spinner_preference_outlined;
+ case 4 -> layoutRes = R.layout.settings_expressive_spinner_preference_outlined;
+ case 5 -> layoutRes = R.layout.settings_expressive_spinner_preference_full_outlined;
+ default -> layoutRes = R.layout.settings_spinner_preference;
+ }
+ }
+ setLayoutResource(layoutRes);
}
@Override
@@ -115,6 +145,12 @@ public class SettingsSpinnerPreference extends Preference
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final Spinner spinner = (Spinner) holder.findViewById(R.id.spinner);
+ if (spinner == null) {
+ return;
+ }
+ if (mAdapter != null) {
+ mAdapter.setSelectedPosition(mPosition);
+ }
spinner.setAdapter(mAdapter);
spinner.setSelection(mPosition);
spinner.setOnItemSelectedListener(mOnSelectedListener);
@@ -140,20 +176,22 @@ public class SettingsSpinnerPreference extends Preference
private final AdapterView.OnItemSelectedListener mOnSelectedListener =
new AdapterView.OnItemSelectedListener() {
- @Override
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- if (mPosition == position) return;
- mPosition = position;
- if (mListener != null) {
- mListener.onItemSelected(parent, view, position, id);
- }
- }
+ @Override
+ public void onItemSelected(
+ AdapterView<?> parent, View view, int position, long id) {
+ if (mPosition == position) return;
+ mPosition = position;
+ mAdapter.setSelectedPosition(mPosition);
+ if (mListener != null) {
+ mListener.onItemSelected(parent, view, position, id);
+ }
+ }
- @Override
- public void onNothingSelected(AdapterView<?> parent) {
- if (mListener != null) {
- mListener.onNothingSelected(parent);
- }
- }
- };
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ if (mListener != null) {
+ mListener.onNothingSelected(parent);
+ }
+ }
+ };
}
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_expressive_spinner_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_expressive_spinner_background.xml
new file mode 100644
index 000000000000..139418b38e03
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_expressive_spinner_background.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<ripple
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+
+ <item android:id="@android:id/background">
+ <layer-list
+ android:paddingMode="stack"
+ android:paddingStart="0dp"
+ android:paddingEnd="@dimen/settingslib_expressive_space_small4">
+ <item>
+ <shape>
+ <corners android:radius="@dimen/settingslib_expressive_radius_full"/>
+ <solid android:color="@color/settingslib_materialColorSecondaryContainer"/>
+ <size android:height="@dimen/settingslib_expressive_space_medium3"/>
+ </shape>
+ </item>
+
+ <item
+ android:gravity="center|end"
+ android:width="@dimen/settingslib_expressive_space_small4"
+ android:height="@dimen/settingslib_expressive_space_small4"
+ android:end="@dimen/settingslib_expressive_space_small1">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="@color/settingslib_materialColorOnSecondaryContainer">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6 -1.41,-1.41z"/>
+ </vector>
+ </item>
+ </layer-list>
+ </item>
+</ripple>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_expressive_spinner_background_outlined.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_expressive_spinner_background_outlined.xml
new file mode 100644
index 000000000000..f32e13e7f83a
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_expressive_spinner_background_outlined.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<ripple
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+
+ <item android:id="@android:id/background">
+ <layer-list
+ android:paddingMode="stack"
+ android:paddingStart="0dp"
+ android:paddingEnd="@dimen/settingslib_expressive_space_small4">
+ <item>
+ <shape>
+ <corners android:radius="@dimen/settingslib_expressive_radius_full"/>
+ <stroke
+ android:color="@color/settingslib_materialColorOutlineVariant"
+ android:width="1dp"/>
+ <size android:height="@dimen/settingslib_expressive_space_medium3"/>
+ </shape>
+ </item>
+
+ <item
+ android:gravity="center|end"
+ android:width="@dimen/settingslib_expressive_space_small4"
+ android:height="@dimen/settingslib_expressive_space_small4"
+ android:end="@dimen/settingslib_expressive_space_small1">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="@color/settingslib_materialColorPrimary">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6 -1.41,-1.41z"/>
+ </vector>
+ </item>
+ </layer-list>
+ </item>
+</ripple>
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_expressive_spinner_dropdown_background.xml b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_expressive_spinner_dropdown_background.xml
new file mode 100644
index 000000000000..ac38c3e9223b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable-v36/settingslib_expressive_spinner_dropdown_background.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2025 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<ripple
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+
+ <item android:id="@android:id/background">
+ <layer-list
+ android:paddingMode="stack"
+ android:paddingStart="@dimen/settingslib_expressive_space_extrasmall4"
+ android:paddingEnd="@dimen/settingslib_expressive_space_extrasmall4"
+ android:paddingTop="@dimen/settingslib_expressive_space_extrasmall4"
+ android:paddingBottom="@dimen/settingslib_expressive_space_extrasmall4">
+
+ <item>
+ <shape>
+ <corners android:radius="@dimen/settingslib_expressive_radius_large3"/>
+ <solid android:color="@color/settingslib_materialColorSurfaceContainerLow"/>
+ </shape>
+ </item>
+ </layer-list>
+ </item>
+</ripple>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v36/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v36/styles_expressive.xml
index de48f99215fb..9cdbce4a4c78 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v36/styles_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v36/styles_expressive.xml
@@ -31,6 +31,17 @@
<item name="trackTint">@color/settingslib_expressive_color_main_switch_track</item>
</style>
+ <style name="SettingslibSpinnerStyle.Expressive"
+ parent="android:style/Widget.Material.Spinner">
+ <item name="android:background">@drawable/settingslib_expressive_spinner_background</item>
+ <item name="android:popupBackground">@drawable/settingslib_expressive_spinner_dropdown_background</item>
+ <item name="android:dropDownVerticalOffset">@dimen/settingslib_expressive_space_large3</item>
+ </style>
+
+ <style name="SettingslibSpinnerStyle.Expressive.Outlined">
+ <item name="android:background">@drawable/settingslib_expressive_spinner_background_outlined</item>
+ </style>
+
<style name="EntityHeader">
<item name="android:paddingTop">@dimen/settingslib_expressive_space_small4</item>
<item name="android:paddingBottom">@dimen/settingslib_expressive_space_small1</item>
@@ -125,4 +136,4 @@
<item name="android:layout_marginEnd">@dimen/settingslib_expressive_space_extrasmall4</item>
<item name="android:background">@drawable/settingslib_expressive_button_background_outline</item>
</style>
-</resources> \ No newline at end of file
+</resources>
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v36/themes_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v36/themes_expressive.xml
index 5173ebeaa9a1..ffbc65cc622b 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v36/themes_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v36/themes_expressive.xml
@@ -32,9 +32,9 @@
<item name="preferenceTheme">@style/PreferenceTheme.SettingsLib.Expressive</item>
<!-- Set up Spinner style -->
- <!--item name="android:spinnerStyle"></item>
- <item name="android:spinnerItemStyle"></item>
- <item name="android:spinnerDropDownItemStyle"></item-->
+ <item name="android:spinnerStyle">@style/SettingslibSpinnerStyle.Expressive</item>
+ <!--<item name="android:spinnerItemStyle"></item>
+ <item name="android:spinnerDropDownItemStyle"></item>-->
<!-- Set up edge-to-edge configuration for top app bar -->
<item name="android:clipToPadding">false</item>
@@ -66,4 +66,4 @@
<item name="buttonBarNegativeButtonStyle">@style/Widget.SettingsLib.DialogButton.Outline</item>
<item name="buttonBarNeutralButtonStyle">@style/Widget.SettingsLib.DialogButton</item>
</style>
-</resources> \ No newline at end of file
+</resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
index cca43b92ef19..84d61fc86073 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
@@ -501,6 +501,7 @@ open class WifiUtils {
dialogWindowType: Int,
onStartActivity: (intent: Intent) -> Unit,
onAllowed: () -> Unit,
+ onStartAapmActivity: (intent: Intent) -> Unit = onStartActivity,
): Job =
coroutineScope.launch {
val wifiManager = context.getSystemService(WifiManager::class.java) ?: return@launch
@@ -510,7 +511,7 @@ open class WifiUtils {
AdvancedProtectionManager.FEATURE_ID_DISALLOW_WEP,
AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION)
intent.putExtra(DIALOG_WINDOW_TYPE, dialogWindowType)
- withContext(Dispatchers.Main) { onStartActivity(intent) }
+ withContext(Dispatchers.Main) { onStartAapmActivity(intent) }
} else if (wifiManager.isWepSupported == true && wifiManager.queryWepAllowed()) {
withContext(Dispatchers.Main) { onAllowed() }
} else {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 65ede9d804d0..2dcaf088bf6c 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -4080,7 +4080,7 @@ public class SettingsProvider extends ContentProvider {
@VisibleForTesting
final class UpgradeController {
- private static final int SETTINGS_VERSION = 228;
+ private static final int SETTINGS_VERSION = 229;
private final int mUserId;
@@ -6336,6 +6336,52 @@ public class SettingsProvider extends ContentProvider {
currentVersion = 228;
}
+ // Version 228: Migrate WearOS time settings
+ if (currentVersion == 228) {
+ if (getContext()
+ .getPackageManager()
+ .hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+
+ SettingsState global = getGlobalSettingsLocked();
+
+ Setting cwAutoTime =
+ global.getSettingLocked(Global.Wearable.CLOCKWORK_AUTO_TIME);
+ if (!cwAutoTime.isNull()) {
+ boolean phone =
+ String.valueOf(Global.Wearable.SYNC_TIME_FROM_PHONE)
+ .equals(cwAutoTime.getValue());
+ boolean network =
+ String.valueOf(Global.Wearable.SYNC_TIME_FROM_NETWORK)
+ .equals(cwAutoTime.getValue());
+ global.insertSettingLocked(
+ Global.AUTO_TIME,
+ phone || network ? "1" : "0",
+ null,
+ true,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+
+ Setting cwAutoTimeZone =
+ global.getSettingLocked(Global.Wearable.CLOCKWORK_AUTO_TIME_ZONE);
+ if (!cwAutoTimeZone.isNull()) {
+ boolean phone =
+ String.valueOf(Global.Wearable.SYNC_TIME_ZONE_FROM_PHONE)
+ .equals(cwAutoTimeZone.getValue());
+ boolean network =
+ String.valueOf(Global.Wearable.SYNC_TIME_ZONE_FROM_NETWORK)
+ .equals(cwAutoTimeZone.getValue());
+ global.insertSettingLocked(
+ Global.AUTO_TIME_ZONE,
+ phone || network ? "1" : "0",
+ null,
+ true,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ }
+
+ currentVersion = 229;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 91492b2959d8..f65ca3b60818 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -600,6 +600,16 @@ flag {
}
flag {
+ name: "avalanche_replace_hun_when_critical"
+ namespace: "systemui"
+ description: "Fix for replacing a sticky HUN when a critical HUN posted"
+ bug: "403301297"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "indication_text_a11y_fix"
namespace: "systemui"
description: "add double shadow to the indication text at the bottom of the lock screen"
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/ContentOverscrollEffect.kt b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/ContentOverscrollEffect.kt
index cb713ece12a5..5ed72c7d94a2 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/ContentOverscrollEffect.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/gesture/effect/ContentOverscrollEffect.kt
@@ -55,7 +55,12 @@ open class BaseContentOverscrollEffect(
get() = animatable.value
override val isInProgress: Boolean
- get() = overscrollDistance != 0f
+ /**
+ * We need both checks, because [overscrollDistance] can be
+ * - zero while it is already being animated, if the animation starts from 0
+ * - greater than zero without an animation, if the content is still being dragged
+ */
+ get() = overscrollDistance != 0f || animatable.isRunning
override fun applyToScroll(
delta: Offset,
diff --git a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/effect/OffsetOverscrollEffectTest.kt b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/effect/OffsetOverscrollEffectTest.kt
index e7c47fb56130..8a1fa3724d15 100644
--- a/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/effect/OffsetOverscrollEffectTest.kt
+++ b/packages/SystemUI/compose/core/tests/src/com/android/compose/gesture/effect/OffsetOverscrollEffectTest.kt
@@ -16,12 +16,17 @@
package com.android.compose.gesture.effect
+import androidx.compose.foundation.OverscrollEffect
import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.ScrollableState
import androidx.compose.foundation.gestures.rememberScrollableState
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.overscroll
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.platform.LocalDensity
@@ -32,11 +37,14 @@ import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeWithVelocity
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
import kotlin.properties.Delegates
+import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -47,7 +55,13 @@ class OffsetOverscrollEffectTest {
private val BOX_TAG = "box"
- private data class LayoutInfo(val layoutSize: Dp, val touchSlop: Float, val density: Density) {
+ private data class LayoutInfo(
+ val layoutSize: Dp,
+ val touchSlop: Float,
+ val density: Density,
+ val scrollableState: ScrollableState,
+ val overscrollEffect: OverscrollEffect,
+ ) {
fun expectedOffset(currentOffset: Dp): Dp {
return with(density) {
OffsetOverscrollEffect.computeOffset(this, currentOffset.toPx()).toDp()
@@ -55,22 +69,29 @@ class OffsetOverscrollEffectTest {
}
}
- private fun setupOverscrollableBox(scrollableOrientation: Orientation): LayoutInfo {
+ private fun setupOverscrollableBox(
+ scrollableOrientation: Orientation,
+ canScroll: () -> Boolean,
+ ): LayoutInfo {
val layoutSize: Dp = 200.dp
var touchSlop: Float by Delegates.notNull()
// The draggable touch slop, i.e. the min px distance a touch pointer must move before it is
// detected as a drag event.
lateinit var density: Density
+ lateinit var scrollableState: ScrollableState
+ lateinit var overscrollEffect: OverscrollEffect
+
rule.setContent {
density = LocalDensity.current
touchSlop = LocalViewConfiguration.current.touchSlop
- val overscrollEffect = rememberOffsetOverscrollEffect()
+ scrollableState = rememberScrollableState { if (canScroll()) it else 0f }
+ overscrollEffect = rememberOffsetOverscrollEffect()
Box(
Modifier.overscroll(overscrollEffect)
// A scrollable that does not consume the scroll gesture.
.scrollable(
- state = rememberScrollableState { 0f },
+ state = scrollableState,
orientation = scrollableOrientation,
overscrollEffect = overscrollEffect,
)
@@ -78,12 +99,16 @@ class OffsetOverscrollEffectTest {
.testTag(BOX_TAG)
)
}
- return LayoutInfo(layoutSize, touchSlop, density)
+ return LayoutInfo(layoutSize, touchSlop, density, scrollableState, overscrollEffect)
}
@Test
fun applyVerticalOffset_duringVerticalOverscroll() {
- val info = setupOverscrollableBox(scrollableOrientation = Orientation.Vertical)
+ val info =
+ setupOverscrollableBox(
+ scrollableOrientation = Orientation.Vertical,
+ canScroll = { false },
+ )
rule.onNodeWithTag(BOX_TAG).assertTopPositionInRootIsEqualTo(0.dp)
@@ -99,7 +124,11 @@ class OffsetOverscrollEffectTest {
@Test
fun applyNoOffset_duringHorizontalOverscroll() {
- val info = setupOverscrollableBox(scrollableOrientation = Orientation.Vertical)
+ val info =
+ setupOverscrollableBox(
+ scrollableOrientation = Orientation.Vertical,
+ canScroll = { false },
+ )
rule.onNodeWithTag(BOX_TAG).assertTopPositionInRootIsEqualTo(0.dp)
@@ -113,7 +142,11 @@ class OffsetOverscrollEffectTest {
@Test
fun backToZero_afterOverscroll() {
- val info = setupOverscrollableBox(scrollableOrientation = Orientation.Vertical)
+ val info =
+ setupOverscrollableBox(
+ scrollableOrientation = Orientation.Vertical,
+ canScroll = { false },
+ )
rule.onRoot().performTouchInput {
down(center)
@@ -131,7 +164,11 @@ class OffsetOverscrollEffectTest {
@Test
fun offsetOverscroll_followTheTouchPointer() {
- val info = setupOverscrollableBox(scrollableOrientation = Orientation.Vertical)
+ val info =
+ setupOverscrollableBox(
+ scrollableOrientation = Orientation.Vertical,
+ canScroll = { false },
+ )
// First gesture, drag down.
rule.onRoot().performTouchInput {
@@ -165,4 +202,130 @@ class OffsetOverscrollEffectTest {
.onNodeWithTag(BOX_TAG)
.assertTopPositionInRootIsEqualTo(info.expectedOffset(-info.layoutSize))
}
+
+ @Test
+ fun isScrollInProgress_overscroll() = runTest {
+ val info =
+ setupOverscrollableBox(
+ scrollableOrientation = Orientation.Vertical,
+ canScroll = { false },
+ )
+
+ // Start a swipe gesture, and swipe down to start an overscroll.
+ rule.onRoot().performTouchInput {
+ down(center)
+ moveBy(Offset(0f, info.touchSlop + info.layoutSize.toPx() / 2))
+ }
+
+ assertThat(info.scrollableState.isScrollInProgress).isTrue()
+ assertThat(info.overscrollEffect.isInProgress).isTrue()
+
+ // Finish the swipe gesture.
+ rule.onRoot().performTouchInput { up() }
+
+ assertThat(info.scrollableState.isScrollInProgress).isFalse()
+ assertThat(info.overscrollEffect.isInProgress).isTrue()
+
+ // Wait until the overscroll returns to idle.
+ rule.awaitIdle()
+
+ assertThat(info.scrollableState.isScrollInProgress).isFalse()
+ assertThat(info.overscrollEffect.isInProgress).isFalse()
+ }
+
+ @Test
+ fun isScrollInProgress_scroll() = runTest {
+ val info =
+ setupOverscrollableBox(
+ scrollableOrientation = Orientation.Vertical,
+ canScroll = { true },
+ )
+
+ rule.onNodeWithTag(BOX_TAG).assertTopPositionInRootIsEqualTo(0.dp)
+
+ // Start a swipe gesture, and swipe down to scroll.
+ rule.onRoot().performTouchInput {
+ down(center)
+ moveBy(Offset(0f, info.touchSlop + info.layoutSize.toPx() / 2))
+ }
+
+ assertThat(info.scrollableState.isScrollInProgress).isTrue()
+ assertThat(info.overscrollEffect.isInProgress).isFalse()
+
+ // Finish the swipe gesture.
+ rule.onRoot().performTouchInput { up() }
+
+ assertThat(info.scrollableState.isScrollInProgress).isFalse()
+ assertThat(info.overscrollEffect.isInProgress).isTrue()
+
+ // Wait until the overscroll returns to idle.
+ rule.awaitIdle()
+
+ assertThat(info.scrollableState.isScrollInProgress).isFalse()
+ assertThat(info.overscrollEffect.isInProgress).isFalse()
+ }
+
+ @Test
+ fun isScrollInProgress_flingToScroll() = runTest {
+ val info =
+ setupOverscrollableBox(
+ scrollableOrientation = Orientation.Vertical,
+ canScroll = { true },
+ )
+
+ rule.onNodeWithTag(BOX_TAG).assertTopPositionInRootIsEqualTo(0.dp)
+
+ // Swipe down and leave some velocity to start a fling.
+ rule.onRoot().performTouchInput {
+ swipeWithVelocity(
+ Offset.Zero,
+ Offset(0f, info.touchSlop + info.layoutSize.toPx() / 2),
+ endVelocity = 100f,
+ )
+ }
+
+ assertThat(info.scrollableState.isScrollInProgress).isTrue()
+ assertThat(info.overscrollEffect.isInProgress).isFalse()
+
+ // Wait until the fling is finished.
+ rule.awaitIdle()
+
+ assertThat(info.scrollableState.isScrollInProgress).isFalse()
+ assertThat(info.overscrollEffect.isInProgress).isFalse()
+ }
+
+ @Test
+ fun isScrollInProgress_flingToOverscroll() = runTest {
+ // Start with a scrollable state.
+ var canScroll by mutableStateOf(true)
+ val info =
+ setupOverscrollableBox(scrollableOrientation = Orientation.Vertical) { canScroll }
+
+ rule.onNodeWithTag(BOX_TAG).assertTopPositionInRootIsEqualTo(0.dp)
+
+ // Swipe down and leave some velocity to start a fling.
+ rule.onRoot().performTouchInput {
+ swipeWithVelocity(
+ Offset.Zero,
+ Offset(0f, info.touchSlop + info.layoutSize.toPx() / 2),
+ endVelocity = 100f,
+ )
+ }
+
+ assertThat(info.scrollableState.isScrollInProgress).isTrue()
+ assertThat(info.overscrollEffect.isInProgress).isFalse()
+
+ // The fling reaches the end of the scrollable region, and an overscroll starts.
+ canScroll = false
+ rule.mainClock.advanceTimeUntil { !info.scrollableState.isScrollInProgress }
+
+ assertThat(info.scrollableState.isScrollInProgress).isFalse()
+ assertThat(info.overscrollEffect.isInProgress).isTrue()
+
+ // Wait until the overscroll returns to idle.
+ rule.awaitIdle()
+
+ assertThat(info.scrollableState.isScrollInProgress).isFalse()
+ assertThat(info.overscrollEffect.isInProgress).isFalse()
+ }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index d903c3d16fdb..748c3b89649a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -53,7 +53,7 @@ import com.android.systemui.statusbar.notification.icon.ui.viewbinder.Notificati
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
import com.android.systemui.statusbar.notification.promoted.AODPromotedNotification
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
import com.android.systemui.statusbar.notification.promoted.ui.viewmodel.AODPromotedNotificationViewModel
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
@@ -111,7 +111,7 @@ constructor(
@Composable
fun AodPromotedNotificationArea(modifier: Modifier = Modifier) {
- if (!PromotedNotificationUiAod.isEnabled) {
+ if (!PromotedNotificationUi.isEnabled) {
return
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
deleted file mode 100644
index e1ee59ba0626..000000000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.notifications.ui.composable
-
-import androidx.compose.animation.core.Animatable
-import androidx.compose.animation.core.tween
-import androidx.compose.foundation.gestures.FlingBehavior
-import androidx.compose.foundation.gestures.Orientation
-import androidx.compose.foundation.gestures.ScrollableDefaults
-import androidx.compose.foundation.layout.offset
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
-import androidx.compose.ui.input.nestedscroll.NestedScrollSource
-import androidx.compose.ui.input.nestedscroll.nestedScroll
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.unit.IntOffset
-import androidx.compose.ui.unit.Velocity
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.util.fastCoerceAtLeast
-import com.android.compose.nestedscroll.OnStopScope
-import com.android.compose.nestedscroll.PriorityNestedScrollConnection
-import com.android.compose.nestedscroll.ScrollController
-import kotlin.math.max
-import kotlin.math.roundToInt
-import kotlin.math.tanh
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.launch
-
-@Composable
-fun Modifier.stackVerticalOverscroll(
- coroutineScope: CoroutineScope,
- canScrollForward: () -> Boolean,
-): Modifier {
- val screenHeight =
- with(LocalDensity.current) { LocalConfiguration.current.screenHeightDp.dp.toPx() }
- val overscrollOffset = remember { Animatable(0f) }
- val flingBehavior = ScrollableDefaults.flingBehavior()
- val stackNestedScrollConnection =
- remember(flingBehavior) {
- NotificationStackNestedScrollConnection(
- stackOffset = { overscrollOffset.value },
- canScrollForward = canScrollForward,
- onScroll = { offsetAvailable ->
- coroutineScope.launch {
- val maxProgress = screenHeight * 0.2f
- val tilt = 3f
- var offset =
- overscrollOffset.value +
- maxProgress * tanh(x = offsetAvailable / (maxProgress * tilt))
- offset = max(offset, -1f * maxProgress)
- overscrollOffset.snapTo(offset)
- }
- },
- onStop = { velocityAvailable ->
- coroutineScope.launch {
- overscrollOffset.animateTo(
- targetValue = 0f,
- initialVelocity = velocityAvailable,
- animationSpec = tween(),
- )
- }
- },
- flingBehavior = flingBehavior,
- )
- }
-
- return this.then(
- Modifier.nestedScroll(
- remember {
- object : NestedScrollConnection {
- override suspend fun onPostFling(
- consumed: Velocity,
- available: Velocity,
- ): Velocity {
- return if (available.y < 0f && !canScrollForward()) {
- overscrollOffset.animateTo(
- targetValue = 0f,
- initialVelocity = available.y,
- animationSpec = tween(),
- )
- available
- } else {
- Velocity.Zero
- }
- }
- }
- }
- )
- .nestedScroll(stackNestedScrollConnection)
- .offset { IntOffset(x = 0, y = overscrollOffset.value.roundToInt()) }
- )
-}
-
-fun NotificationStackNestedScrollConnection(
- stackOffset: () -> Float,
- canScrollForward: () -> Boolean,
- onStart: (Float) -> Unit = {},
- onScroll: (Float) -> Unit,
- onStop: (Float) -> Unit = {},
- flingBehavior: FlingBehavior,
-): PriorityNestedScrollConnection {
- return PriorityNestedScrollConnection(
- orientation = Orientation.Vertical,
- canStartPreScroll = { _, _, _ -> false },
- canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ ->
- offsetAvailable < 0f && offsetBeforeStart < 0f && !canScrollForward()
- },
- onStart = { firstScroll ->
- onStart(firstScroll)
- object : ScrollController {
- override fun onScroll(deltaScroll: Float, source: NestedScrollSource): Float {
- val minOffset = 0f
- val consumed = deltaScroll.fastCoerceAtLeast(minOffset - stackOffset())
- if (consumed != 0f) {
- onScroll(consumed)
- }
- return consumed
- }
-
- override suspend fun OnStopScope.onStop(initialVelocity: Float): Float {
- val consumedByScroll = flingToScroll(initialVelocity, flingBehavior)
- onStop(initialVelocity - consumedByScroll)
- return initialVelocity
- }
-
- override fun onCancel() {
- onStop(0f)
- }
-
- override fun canStopOnPreFling() = false
- }
- },
- )
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 79b346439d5d..2f9cfb6aa211 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -78,6 +78,7 @@ import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
@@ -92,7 +93,11 @@ import com.android.compose.animation.scene.LowestZIndexContentPicker
import com.android.compose.animation.scene.SceneTransitionLayoutState
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.gesture.NestedScrollableBound
+import com.android.compose.gesture.effect.OffsetOverscrollEffect
+import com.android.compose.gesture.effect.rememberOffsetOverscrollEffect
import com.android.compose.modifiers.thenIf
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING
import com.android.systemui.common.ui.compose.windowinsets.LocalScreenCornerRadius
import com.android.systemui.res.R
import com.android.systemui.scene.session.ui.composable.SaveableSession
@@ -288,17 +293,19 @@ fun ContentScope.NotificationScrollingStack(
shadeSession: SaveableSession,
stackScrollView: NotificationScrollView,
viewModel: NotificationsPlaceholderViewModel,
+ jankMonitor: InteractionJankMonitor,
maxScrimTop: () -> Float,
shouldPunchHoleBehindScrim: Boolean,
stackTopPadding: Dp,
stackBottomPadding: Dp,
+ modifier: Modifier = Modifier,
shouldFillMaxSize: Boolean = true,
shouldIncludeHeadsUpSpace: Boolean = true,
shouldShowScrim: Boolean = true,
supportNestedScrolling: Boolean,
onEmptySpaceClick: (() -> Unit)? = null,
- modifier: Modifier = Modifier,
) {
+ val composeViewRoot = LocalView.current
val coroutineScope = shadeSession.sessionCoroutineScope()
val density = LocalDensity.current
val screenCornerRadius = LocalScreenCornerRadius.current
@@ -477,6 +484,21 @@ fun ContentScope.NotificationScrollingStack(
)
}
+ val overScrollEffect: OffsetOverscrollEffect = rememberOffsetOverscrollEffect()
+ // whether the stack is moving due to a swipe or fling
+ val isScrollInProgress =
+ scrollState.isScrollInProgress || overScrollEffect.isInProgress || scrimOffset.isRunning
+
+ LaunchedEffect(isScrollInProgress) {
+ if (isScrollInProgress) {
+ jankMonitor.begin(composeViewRoot, CUJ_NOTIFICATION_SHADE_SCROLL_FLING)
+ debugLog(viewModel) { "STACK scroll begins" }
+ } else {
+ debugLog(viewModel) { "STACK scroll ends" }
+ jankMonitor.end(CUJ_NOTIFICATION_SHADE_SCROLL_FLING)
+ }
+ }
+
Box(
modifier =
modifier
@@ -577,8 +599,7 @@ fun ContentScope.NotificationScrollingStack(
.thenIf(supportNestedScrolling) {
Modifier.nestedScroll(scrimNestedScrollConnection)
}
- .stackVerticalOverscroll(coroutineScope) { scrollState.canScrollForward }
- .verticalScroll(scrollState)
+ .verticalScroll(scrollState, overscrollEffect = overScrollEffect)
.padding(top = stackTopPadding, bottom = stackBottomPadding)
.fillMaxWidth()
.onGloballyPositioned { coordinates ->
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
index 7cd6c6b47f2a..6d37e0affd6a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeOverlay.kt
@@ -29,6 +29,7 @@ import com.android.compose.animation.scene.ContentScope
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
+import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.composable.blueprint.rememberBurnIn
import com.android.systemui.keyguard.ui.composable.section.DefaultClockSection
@@ -49,6 +50,7 @@ import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.ui.composable.Overlay
import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.composable.OverlayShadeHeader
+import com.android.systemui.shade.ui.composable.isFullWidthShade
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
import com.android.systemui.util.Utils
import dagger.Lazy
@@ -68,6 +70,7 @@ constructor(
private val keyguardClockViewModel: KeyguardClockViewModel,
private val mediaCarouselController: MediaCarouselController,
@Named(QUICK_QS_PANEL) private val mediaHost: Lazy<MediaHost>,
+ private val jankMonitor: InteractionJankMonitor,
) : Overlay {
override val key = Overlays.NotificationsShade
@@ -117,7 +120,7 @@ constructor(
) {
Box {
Column {
- if (viewModel.showClock) {
+ if (isFullWidthShade()) {
val burnIn = rememberBurnIn(keyguardClockViewModel)
with(clockSection) {
@@ -145,6 +148,7 @@ constructor(
shadeSession = shadeSession,
stackScrollView = stackScrollView.get(),
viewModel = placeholderViewModel,
+ jankMonitor = jankMonitor,
maxScrimTop = { 0f },
shouldPunchHoleBehindScrim = false,
stackTopPadding = notificationStackPadding,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 0a711487ccb1..d667f68e4fdd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -75,6 +75,7 @@ import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.modifiers.thenIf
import com.android.compose.windowsizeclass.LocalWindowSizeClass
+import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
import com.android.systemui.compose.modifiers.sysuiResTag
@@ -126,6 +127,7 @@ constructor(
private val contentViewModelFactory: QuickSettingsSceneContentViewModel.Factory,
private val mediaCarouselController: MediaCarouselController,
@Named(MediaModule.QS_PANEL) private val mediaHost: MediaHost,
+ private val jankMonitor: InteractionJankMonitor,
) : ExclusiveActivatable(), Scene {
override val key = Scenes.QuickSettings
@@ -165,6 +167,7 @@ constructor(
mediaHost = mediaHost,
modifier = modifier,
shadeSession = shadeSession,
+ jankMonitor = jankMonitor,
)
}
@@ -186,6 +189,7 @@ private fun ContentScope.QuickSettingsScene(
mediaHost: MediaHost,
modifier: Modifier = Modifier,
shadeSession: SaveableSession,
+ jankMonitor: InteractionJankMonitor,
) {
val cutoutLocation = LocalDisplayCutout.current.location
val brightnessMirrorShowing by brightnessMirrorViewModel.isShowing.collectAsStateWithLifecycle()
@@ -432,6 +436,7 @@ private fun ContentScope.QuickSettingsScene(
shadeSession = shadeSession,
stackScrollView = notificationStackScrollView,
viewModel = notificationsPlaceholderViewModel,
+ jankMonitor = jankMonitor,
maxScrimTop = { minNotificationStackTop.toFloat() },
shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
stackTopPadding = notificationStackPadding,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 885d34fb95c9..60e32d7ce824 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -73,6 +73,7 @@ import com.android.compose.animation.scene.animateSceneFloatAsState
import com.android.compose.animation.scene.content.state.TransitionState
import com.android.compose.modifiers.padding
import com.android.compose.modifiers.thenIf
+import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.battery.BatteryMeterViewController
import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
import com.android.systemui.common.ui.compose.windowinsets.LocalDisplayCutout
@@ -145,6 +146,7 @@ constructor(
private val mediaCarouselController: MediaCarouselController,
@Named(QUICK_QS_PANEL) private val qqsMediaHost: MediaHost,
@Named(QS_PANEL) private val qsMediaHost: MediaHost,
+ private val jankMonitor: InteractionJankMonitor,
) : ExclusiveActivatable(), Scene {
override val key = Scenes.Shade
@@ -182,6 +184,7 @@ constructor(
mediaCarouselController = mediaCarouselController,
qqsMediaHost = qqsMediaHost,
qsMediaHost = qsMediaHost,
+ jankMonitor = jankMonitor,
modifier = modifier,
shadeSession = shadeSession,
usingCollapsedLandscapeMedia =
@@ -212,6 +215,7 @@ private fun ContentScope.ShadeScene(
mediaCarouselController: MediaCarouselController,
qqsMediaHost: MediaHost,
qsMediaHost: MediaHost,
+ jankMonitor: InteractionJankMonitor,
modifier: Modifier = Modifier,
shadeSession: SaveableSession,
usingCollapsedLandscapeMedia: Boolean,
@@ -229,6 +233,7 @@ private fun ContentScope.ShadeScene(
modifier = modifier,
shadeSession = shadeSession,
usingCollapsedLandscapeMedia = usingCollapsedLandscapeMedia,
+ jankMonitor = jankMonitor,
)
is ShadeMode.Split ->
SplitShade(
@@ -240,6 +245,7 @@ private fun ContentScope.ShadeScene(
mediaHost = qsMediaHost,
modifier = modifier,
shadeSession = shadeSession,
+ jankMonitor = jankMonitor,
)
is ShadeMode.Dual -> error("Dual shade is implemented separately as an overlay.")
}
@@ -253,6 +259,7 @@ private fun ContentScope.SingleShade(
notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
mediaCarouselController: MediaCarouselController,
mediaHost: MediaHost,
+ jankMonitor: InteractionJankMonitor,
modifier: Modifier = Modifier,
shadeSession: SaveableSession,
usingCollapsedLandscapeMedia: Boolean,
@@ -379,6 +386,7 @@ private fun ContentScope.SingleShade(
shadeSession = shadeSession,
stackScrollView = notificationStackScrollView,
viewModel = notificationsPlaceholderViewModel,
+ jankMonitor = jankMonitor,
maxScrimTop = { maxNotifScrimTop.toFloat() },
shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
stackTopPadding = notificationStackPadding,
@@ -419,6 +427,7 @@ private fun ContentScope.SplitShade(
mediaHost: MediaHost,
modifier: Modifier = Modifier,
shadeSession: SaveableSession,
+ jankMonitor: InteractionJankMonitor,
) {
val isCustomizing by viewModel.qsSceneAdapter.isCustomizing.collectAsStateWithLifecycle()
val isQsEnabled by viewModel.isQsEnabled.collectAsStateWithLifecycle()
@@ -596,6 +605,7 @@ private fun ContentScope.SplitShade(
shadeSession = shadeSession,
stackScrollView = notificationStackScrollView,
viewModel = notificationsPlaceholderViewModel,
+ jankMonitor = jankMonitor,
maxScrimTop = { 0f },
stackTopPadding = notificationStackPadding,
stackBottomPadding = notificationStackPadding,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/util/UserTouchActivityNotifierTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/util/UserTouchActivityNotifierTest.kt
new file mode 100644
index 000000000000..581f3cb172fe
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/util/UserTouchActivityNotifierTest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.util
+
+import android.testing.AndroidTestingRunner
+import android.view.MotionEvent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.runTest
+import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.power.data.repository.fakePowerRepository
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class UserTouchActivityNotifierTest : SysuiTestCase() {
+ private val kosmos: Kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ @Test
+ fun firstEventTriggersNotify() =
+ kosmos.runTest { sendEventAndVerify(0, MotionEvent.ACTION_MOVE, true) }
+
+ @Test
+ fun secondEventTriggersRateLimited() =
+ kosmos.runTest {
+ var eventTime = 0L
+
+ sendEventAndVerify(eventTime, MotionEvent.ACTION_MOVE, true)
+ eventTime += 50
+ sendEventAndVerify(eventTime, MotionEvent.ACTION_MOVE, false)
+ eventTime += USER_TOUCH_ACTIVITY_RATE_LIMIT
+ sendEventAndVerify(eventTime, MotionEvent.ACTION_MOVE, true)
+ }
+
+ @Test
+ fun overridingActionNotifies() =
+ kosmos.runTest {
+ var eventTime = 0L
+ sendEventAndVerify(eventTime, MotionEvent.ACTION_MOVE, true)
+ sendEventAndVerify(eventTime, MotionEvent.ACTION_DOWN, true)
+ sendEventAndVerify(eventTime, MotionEvent.ACTION_UP, true)
+ sendEventAndVerify(eventTime, MotionEvent.ACTION_CANCEL, true)
+ }
+
+ private fun sendEventAndVerify(eventTime: Long, action: Int, shouldBeHandled: Boolean) {
+ kosmos.fakePowerRepository.userTouchRegistered = false
+ val motionEvent = MotionEvent.obtain(0, eventTime, action, 0f, 0f, 0)
+ kosmos.userTouchActivityNotifier.notifyActivity(motionEvent)
+
+ if (shouldBeHandled) {
+ assertThat(kosmos.fakePowerRepository.userTouchRegistered).isTrue()
+ } else {
+ assertThat(kosmos.fakePowerRepository.userTouchRegistered).isFalse()
+ }
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt
index adce9d65cbe0..e89c05f3a84d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt
@@ -16,6 +16,9 @@
package com.android.systemui.keyguard.ui.viewmodel
+import android.content.res.Configuration
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -44,7 +47,7 @@ class KeyguardSmartspaceViewModelTest : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
val underTest = kosmos.keyguardSmartspaceViewModel
- val res = context.resources
+ @Mock private lateinit var mockConfiguration: Configuration
@Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController
@@ -119,4 +122,63 @@ class KeyguardSmartspaceViewModelTest : SysuiTestCase() {
assertThat(isShadeLayoutWide).isFalse()
}
}
+
+ @Test
+ @DisableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
+ fun dateWeatherBelowSmallClock_smartspacelayoutflag_off_true() {
+ val result = KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(mockConfiguration)
+
+ assertThat(result).isTrue()
+ }
+
+ @Test
+ @EnableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
+ fun dateWeatherBelowSmallClock_defaultFontAndDisplaySize_false() {
+ val fontScale = 1.0f
+ val screenWidthDp = 347
+ mockConfiguration.fontScale = fontScale
+ mockConfiguration.screenWidthDp = screenWidthDp
+
+ val result = KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(mockConfiguration)
+
+ assertThat(result).isFalse()
+ }
+
+ @Test
+ @EnableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
+ fun dateWeatherBelowSmallClock_variousFontAndDisplaySize_false() {
+ mockConfiguration.fontScale = 1.0f
+ mockConfiguration.screenWidthDp = 347
+ val result1 = KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(mockConfiguration)
+ assertThat(result1).isFalse()
+
+ mockConfiguration.fontScale = 1.2f
+ mockConfiguration.screenWidthDp = 347
+ val result2 = KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(mockConfiguration)
+ assertThat(result2).isFalse()
+
+ mockConfiguration.fontScale = 1.7f
+ mockConfiguration.screenWidthDp = 412
+ val result3 = KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(mockConfiguration)
+ assertThat(result3).isFalse()
+ }
+
+ @Test
+ @EnableFlags(com.android.systemui.shared.Flags.FLAG_CLOCK_REACTIVE_SMARTSPACE_LAYOUT)
+ fun dateWeatherBelowSmallClock_variousFontAndDisplaySize_true() {
+ mockConfiguration.fontScale = 1.0f
+ mockConfiguration.screenWidthDp = 310
+ val result1 = KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(mockConfiguration)
+ assertThat(result1).isTrue()
+
+ mockConfiguration.fontScale = 1.5f
+ mockConfiguration.screenWidthDp = 347
+ val result2 = KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(mockConfiguration)
+ assertThat(result2).isTrue()
+
+ mockConfiguration.fontScale = 2.0f
+ mockConfiguration.screenWidthDp = 411
+ val result3 = KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(mockConfiguration)
+ assertThat(result3).isTrue()
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt
index 04ef1be9c057..ab605c0ea14e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/ShareToAppPermissionDialogDelegateTest.kt
@@ -18,12 +18,17 @@ package com.android.systemui.mediaprojection.permission
import android.app.AlertDialog
import android.media.projection.MediaProjectionConfig
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.testing.TestableLooper
import android.view.WindowManager
import android.widget.Spinner
import android.widget.TextView
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.res.R
@@ -32,6 +37,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertEquals
import org.junit.After
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
@@ -41,6 +47,8 @@ import org.mockito.kotlin.mock
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class ShareToAppPermissionDialogDelegateTest : SysuiTestCase() {
+ @get:Rule val checkFlagRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
private lateinit var dialog: AlertDialog
private val appName = "Test App"
@@ -51,6 +59,8 @@ class ShareToAppPermissionDialogDelegateTest : SysuiTestCase() {
R.string.media_projection_entry_app_permission_dialog_option_text_entire_screen
private val resIdSingleAppDisabled =
R.string.media_projection_entry_app_permission_dialog_single_app_disabled
+ private val resIdSingleAppNotSupported =
+ R.string.media_projection_entry_app_permission_dialog_single_app_not_supported
@After
fun teardown() {
@@ -78,6 +88,7 @@ class ShareToAppPermissionDialogDelegateTest : SysuiTestCase() {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_MEDIA_PROJECTION_GREY_ERROR_TEXT)
fun showDialog_disableSingleApp() {
setUpAndShowDialog(
mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay()
@@ -98,10 +109,34 @@ class ShareToAppPermissionDialogDelegateTest : SysuiTestCase() {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_MEDIA_PROJECTION_GREY_ERROR_TEXT)
+ fun showDialog_disableSingleApp_appNotSupported() {
+ setUpAndShowDialog(
+ mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay()
+ )
+
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
+ val secondOptionWarningText =
+ spinner.adapter
+ .getDropDownView(1, null, spinner)
+ .findViewById<TextView>(android.R.id.text2)
+ ?.text
+
+ // check that the first option is full screen and enabled
+ assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
+
+ // check that the second option is single app and disabled
+ assertEquals(
+ context.getString(resIdSingleAppNotSupported, appName),
+ secondOptionWarningText,
+ )
+ }
+
+ @Test
fun showDialog_disableSingleApp_forceShowPartialScreenShareTrue() {
setUpAndShowDialog(
mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(),
- overrideDisableSingleAppOption = true
+ overrideDisableSingleAppOption = true,
)
val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
@@ -161,7 +196,7 @@ class ShareToAppPermissionDialogDelegateTest : SysuiTestCase() {
appName,
overrideDisableSingleAppOption,
hostUid = 12345,
- mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>()
+ mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>(),
)
dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt
index 6495b66cc148..17cdb8dd592d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/mediaprojection/permission/SystemCastPermissionDialogDelegateTest.kt
@@ -18,12 +18,17 @@ package com.android.systemui.mediaprojection.permission
import android.app.AlertDialog
import android.media.projection.MediaProjectionConfig
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.testing.TestableLooper
import android.view.WindowManager
import android.widget.Spinner
import android.widget.TextView
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
import com.android.systemui.res.R
@@ -32,6 +37,7 @@ import com.android.systemui.statusbar.phone.SystemUIDialog
import com.google.common.truth.Truth.assertThat
import kotlin.test.assertEquals
import org.junit.After
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.kotlin.mock
@@ -41,6 +47,8 @@ import org.mockito.kotlin.mock
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class SystemCastPermissionDialogDelegateTest : SysuiTestCase() {
+ @get:Rule val checkFlagRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
private lateinit var dialog: AlertDialog
private val appName = "Test App"
@@ -51,6 +59,8 @@ class SystemCastPermissionDialogDelegateTest : SysuiTestCase() {
R.string.media_projection_entry_cast_permission_dialog_option_text_entire_screen
private val resIdSingleAppDisabled =
R.string.media_projection_entry_app_permission_dialog_single_app_disabled
+ private val resIdSingleAppNotSupported =
+ R.string.media_projection_entry_app_permission_dialog_single_app_not_supported
@After
fun teardown() {
@@ -78,6 +88,7 @@ class SystemCastPermissionDialogDelegateTest : SysuiTestCase() {
}
@Test
+ @RequiresFlagsDisabled(Flags.FLAG_MEDIA_PROJECTION_GREY_ERROR_TEXT)
fun showDialog_disableSingleApp() {
setUpAndShowDialog(
mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay()
@@ -98,6 +109,30 @@ class SystemCastPermissionDialogDelegateTest : SysuiTestCase() {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_MEDIA_PROJECTION_GREY_ERROR_TEXT)
+ fun showDialog_disableSingleApp_appNotSupported() {
+ setUpAndShowDialog(
+ mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay()
+ )
+
+ val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_options)
+ val secondOptionWarningText =
+ spinner.adapter
+ .getDropDownView(1, null, spinner)
+ .findViewById<TextView>(android.R.id.text2)
+ ?.text
+
+ // check that the first option is full screen and enabled
+ assertEquals(context.getString(resIdFullScreen), spinner.selectedItem)
+
+ // check that the second option is single app and disabled
+ assertEquals(
+ context.getString(resIdSingleAppNotSupported, appName),
+ secondOptionWarningText,
+ )
+ }
+
+ @Test
fun showDialog_disableSingleApp_forceShowPartialScreenShareTrue() {
setUpAndShowDialog(
mediaProjectionConfig = MediaProjectionConfig.createConfigForDefaultDisplay(),
@@ -169,7 +204,7 @@ class SystemCastPermissionDialogDelegateTest : SysuiTestCase() {
SystemUIDialog.setDialogSize(dialog)
dialog.window?.addSystemFlags(
- WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
+ WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
)
delegate.onCreate(dialog, savedInstanceState = null)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
index ffcd95bc7a4a..cd7b658518b6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModelTest.kt
@@ -38,13 +38,10 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.startable.sceneContainerStartable
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.enableDualShade
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.ui.viewmodel.notificationsShadeOverlayContentViewModel
import com.android.systemui.statusbar.disableflags.data.repository.fakeDisableFlagsRepository
-import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository
-import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -116,38 +113,6 @@ class NotificationsShadeOverlayContentViewModelTest : SysuiTestCase() {
}
@Test
- fun showClock_showsOnNarrowScreen() =
- testScope.runTest {
- kosmos.shadeRepository.setShadeLayoutWide(false)
-
- // Shown when notifications are present.
- kosmos.activeNotificationListRepository.setActiveNotifs(1)
- runCurrent()
- assertThat(underTest.showClock).isTrue()
-
- // Hidden when notifications are not present.
- kosmos.activeNotificationListRepository.setActiveNotifs(0)
- runCurrent()
- assertThat(underTest.showClock).isFalse()
- }
-
- @Test
- fun showClock_hidesOnWideScreen() =
- testScope.runTest {
- kosmos.shadeRepository.setShadeLayoutWide(true)
-
- // Hidden when notifications are present.
- kosmos.activeNotificationListRepository.setActiveNotifs(1)
- runCurrent()
- assertThat(underTest.showClock).isFalse()
-
- // Hidden when notifications are not present.
- kosmos.activeNotificationListRepository.setActiveNotifs(0)
- runCurrent()
- assertThat(underTest.showClock).isFalse()
- }
-
- @Test
fun showMedia_activeMedia_true() =
testScope.runTest {
kosmos.mediaFilterRepository.addSelectedUserMediaEntry(MediaData(active = true))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java
index a831e6344a66..fd796a56652b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/screenshot/scroll/ScrollCaptureControllerTest.java
@@ -204,6 +204,21 @@ public class ScrollCaptureControllerTest extends SysuiTestCase {
assertEquals("bottom", 200, screenshot.getBottom());
}
+ @Test
+ public void testCancellation() {
+ ScrollCaptureController controller = new TestScenario()
+ .withPageHeight(100)
+ .withMaxPages(2.5f)
+ .withTileHeight(10)
+ .withAvailableRange(-10, Integer.MAX_VALUE)
+ .createController(mContext);
+
+ ScrollCaptureController.LongScreenshot screenshot =
+ getUnchecked(controller.run(EMPTY_RESPONSE));
+
+ assertEquals("top", -10, screenshot.getTop());
+ assertEquals("bottom", 240, screenshot.getBottom());
+ }
/**
* Build and configure a stubbed controller for each test case.
*/
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationsInteractorTest.kt
index bad33a402ff7..915edc03952d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationsInteractorTest.kt
@@ -32,12 +32,11 @@ import com.android.systemui.kosmos.runTest
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.statusbar.chips.notification.domain.interactor.statusBarNotificationChipsInteractor
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
-import com.android.systemui.statusbar.core.StatusBarRootModernization
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.buildPromotedOngoingEntry
import com.android.systemui.statusbar.notification.domain.interactor.renderNotificationListInteractor
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
-import com.android.systemui.statusbar.phone.ongoingcall.StatusBarChipsModernization
+import com.android.systemui.statusbar.phone.ongoingcall.EnableChipsModernization
import com.android.systemui.statusbar.policy.domain.interactor.sensitiveNotificationProtectionInteractor
import com.android.systemui.statusbar.policy.mockSensitiveNotificationProtectionController
import com.android.systemui.testKosmos
@@ -50,12 +49,8 @@ import org.mockito.kotlin.whenever
@SmallTest
@RunWith(AndroidJUnit4::class)
-@EnableFlags(
- PromotedNotificationUi.FLAG_NAME,
- StatusBarNotifChips.FLAG_NAME,
- StatusBarChipsModernization.FLAG_NAME,
- StatusBarRootModernization.FLAG_NAME,
-)
+@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
+@EnableChipsModernization
class AODPromotedNotificationsInteractorTest : SysuiTestCase() {
private val kosmos = testKosmos().useUnconfinedTestDispatcher()
@@ -111,10 +106,10 @@ class AODPromotedNotificationsInteractorTest : SysuiTestCase() {
renderNotificationListInteractor.setRenderedList(listOf(ronEntry))
- // THEN aod content is sensitive
+ // THEN aod content is redacted
val content by collectLastValue(underTest.content)
assertThat(content).isNotNull()
- assertThat(content?.title).isNull() // SOON: .isEqualTo("REDACTED")
+ assertThat(content!!.title).isEqualTo("REDACTED")
}
@Test
@@ -128,10 +123,10 @@ class AODPromotedNotificationsInteractorTest : SysuiTestCase() {
renderNotificationListInteractor.setRenderedList(listOf(ronEntry))
- // THEN aod content is sensitive
+ // THEN aod content is redacted
val content by collectLastValue(underTest.content)
assertThat(content).isNotNull()
- assertThat(content?.title).isNull() // SOON: .isEqualTo("REDACTED")
+ assertThat(content!!.title).isEqualTo("REDACTED")
}
private fun Kosmos.setKeyguardLocked(locked: Boolean) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
index f7bbf989ad3f..e03dbf54e101 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
@@ -32,7 +32,7 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.collection.EntryAdapter
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
@@ -155,7 +155,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
}
@Test
- @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
fun maxKeyguardNotificationsForPromotedOngoing_onLockscreenSpaceForMinHeightButNotIntrinsicHeight_returnsOne() {
setGapHeight(0f)
// No divider height since we're testing one element where index = 0
@@ -283,7 +283,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
}
@Test
- @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
fun getSpaceNeeded_onLockscreenEnoughSpacePromotedOngoing_intrinsicHeight() {
setGapHeight(0f)
// No divider height since we're testing one element where index = 0
@@ -342,7 +342,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() {
}
@Test
- @EnableFlags(PromotedNotificationUiForceExpanded.FLAG_NAME)
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
fun getSpaceNeeded_onLockscreenSavingSpacePromotedOngoing_minHeight() {
setGapHeight(0f)
// No divider height since we're testing one element where index = 0
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index a31c0bd35453..2875b7e2ae92 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -108,6 +108,9 @@ interface CommunalModule {
const val LAUNCHER_PACKAGE = "launcher_package"
const val SWIPE_TO_HUB = "swipe_to_hub"
const val SHOW_UMO = "show_umo"
+ const val TOUCH_NOTIFICATION_RATE_LIMIT = "TOUCH_NOTIFICATION_RATE_LIMIT"
+
+ const val TOUCH_NOTIFIFCATION_RATE_LIMIT_MS = 100
@Provides
@Communal
@@ -159,5 +162,11 @@ interface CommunalModule {
fun provideShowUmo(@Main resources: Resources): Boolean {
return resources.getBoolean(R.bool.config_showUmoOnHub)
}
+
+ @Provides
+ @Named(TOUCH_NOTIFICATION_RATE_LIMIT)
+ fun providesRateLimit(): Int {
+ return TOUCH_NOTIFIFCATION_RATE_LIMIT_MS
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/UserTouchActivityNotifier.kt b/packages/SystemUI/src/com/android/systemui/communal/util/UserTouchActivityNotifier.kt
new file mode 100644
index 000000000000..fec98a311fbd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/UserTouchActivityNotifier.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.util
+
+import android.view.MotionEvent
+import com.android.systemui.communal.dagger.CommunalModule.Companion.TOUCH_NOTIFICATION_RATE_LIMIT
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import javax.inject.Inject
+import javax.inject.Named
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * {@link UserTouchActivityNotifier} helps rate limit the user activity notifications sent to {@link
+ * PowerManager} from a single touch source.
+ */
+class UserTouchActivityNotifier
+@Inject
+constructor(
+ @Background private val scope: CoroutineScope,
+ private val powerInteractor: PowerInteractor,
+ @Named(TOUCH_NOTIFICATION_RATE_LIMIT) private val rateLimitMs: Int,
+) {
+ private var lastNotification: Long? = null
+
+ fun notifyActivity(event: MotionEvent) {
+ val metered =
+ when (event.action) {
+ MotionEvent.ACTION_CANCEL -> false
+ MotionEvent.ACTION_UP -> false
+ MotionEvent.ACTION_DOWN -> false
+ else -> true
+ }
+
+ if (metered && lastNotification?.let { event.eventTime - it < rateLimitMs } == true) {
+ return
+ }
+
+ lastNotification = event.eventTime
+
+ scope.launch { powerInteractor.onUserTouch() }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
index 02e04aa279d8..21b28a24213f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardClockInteractor.kt
@@ -35,7 +35,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationInteractor
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
import com.android.systemui.statusbar.notification.promoted.domain.interactor.AODPromotedNotificationInteractor
import com.android.systemui.util.kotlin.combine
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
@@ -90,14 +90,14 @@ constructor(
var clock: ClockController? by keyguardClockRepository.clockEventController::clock
private val isAodPromotedNotificationPresent: Flow<Boolean> =
- if (PromotedNotificationUiAod.isEnabled) {
+ if (PromotedNotificationUi.isEnabled) {
aodPromotedNotificationInteractor.isPresent
} else {
flowOf(false)
}
private val areAnyNotificationsPresent: Flow<Boolean> =
- if (PromotedNotificationUiAod.isEnabled) {
+ if (PromotedNotificationUi.isEnabled) {
combine(
activeNotificationsInteractor.areAnyNotificationsPresent,
isAodPromotedNotificationPresent,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index fc5914b02e05..f38a2430b8fc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -128,13 +128,7 @@ object KeyguardBlueprintViewBinder {
cs: ConstraintSet,
constraintLayout: ConstraintLayout,
) {
- val ids =
- listOf(
- sharedR.id.date_smartspace_view,
- sharedR.id.date_smartspace_view_large,
- sharedR.id.weather_smartspace_view,
- sharedR.id.weather_smartspace_view_large,
- )
+ val ids = listOf(sharedR.id.date_smartspace_view, sharedR.id.date_smartspace_view_large)
for (i in ids) {
constraintLayout.getViewById(i)?.visibility = cs.getVisibility(i)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 60460bf68c12..2fdca6bc68d9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -193,7 +193,6 @@ object KeyguardRootViewBinder {
childViews[largeClockId]?.translationY = y
if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
childViews[largeClockDateId]?.translationY = y
- childViews[largeClockWeatherId]?.translationY = y
}
childViews[aodPromotedNotificationId]?.translationY = y
childViews[aodNotificationIconContainerId]?.translationY = y
@@ -584,7 +583,6 @@ object KeyguardRootViewBinder {
private val aodNotificationIconContainerId = R.id.aod_notification_icon_container
private val largeClockId = customR.id.lockscreen_clock_view_large
private val largeClockDateId = sharedR.id.date_smartspace_view_large
- private val largeClockWeatherId = sharedR.id.weather_smartspace_view_large
private val smallClockId = customR.id.lockscreen_clock_view
private val indicationArea = R.id.keyguard_indication_area
private val startButton = R.id.start_button
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
index 5ef2d6fd3256..39fe588d8b6b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -91,14 +91,9 @@ object KeyguardSmartspaceViewBinder {
R.dimen.smartspace_padding_vertical
)
- val smallViewIds =
- listOf(sharedR.id.date_smartspace_view, sharedR.id.weather_smartspace_view)
+ val smallViewId = sharedR.id.date_smartspace_view
- val largeViewIds =
- listOf(
- sharedR.id.date_smartspace_view_large,
- sharedR.id.weather_smartspace_view_large,
- )
+ val largeViewId = sharedR.id.date_smartspace_view_large
launch("$TAG#smartspaceViewModel.burnInLayerVisibility") {
combine(
@@ -109,10 +104,8 @@ object KeyguardSmartspaceViewBinder {
.collect { (visibility, isLargeClock) ->
if (isLargeClock) {
// hide small clock date/weather
- for (viewId in smallViewIds) {
- keyguardRootView.findViewById<View>(viewId)?.let {
- it.visibility = View.GONE
- }
+ keyguardRootView.findViewById<View>(smallViewId)?.let {
+ it.visibility = View.GONE
}
}
}
@@ -130,10 +123,9 @@ object KeyguardSmartspaceViewBinder {
::Pair,
)
.collect { (isLargeClock, clockBounds) ->
- for (id in (if (isLargeClock) smallViewIds else largeViewIds)) {
- keyguardRootView.findViewById<View>(id)?.let {
- it.visibility = View.GONE
- }
+ val viewId = if (isLargeClock) smallViewId else largeViewId
+ keyguardRootView.findViewById<View>(viewId)?.let {
+ it.visibility = View.GONE
}
if (clockBounds == VRectF.ZERO) return@collect
@@ -144,26 +136,26 @@ object KeyguardSmartspaceViewBinder {
sharedR.id.date_smartspace_view_large
)
?.height ?: 0
- for (id in largeViewIds) {
- keyguardRootView.findViewById<View>(id)?.let { view ->
- val viewHeight = view.height
- val offset = (largeDateHeight - viewHeight) / 2
- view.top =
- (clockBounds.bottom + yBuffer + offset).toInt()
- view.bottom = view.top + viewHeight
- }
+
+ keyguardRootView.findViewById<View>(largeViewId)?.let { view ->
+ val viewHeight = view.height
+ val offset = (largeDateHeight - viewHeight) / 2
+ view.top = (clockBounds.bottom + yBuffer + offset).toInt()
+ view.bottom = view.top + viewHeight
}
- } else {
- for (id in smallViewIds) {
- keyguardRootView.findViewById<View>(id)?.let { view ->
- val viewWidth = view.width
- if (view.isLayoutRtl()) {
- view.right = (clockBounds.left - xBuffer).toInt()
- view.left = view.right - viewWidth
- } else {
- view.left = (clockBounds.right + xBuffer).toInt()
- view.right = view.left + viewWidth
- }
+ } else if (
+ !KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(
+ keyguardRootView.resources.configuration
+ )
+ ) {
+ keyguardRootView.findViewById<View>(smallViewId)?.let { view ->
+ val viewWidth = view.width
+ if (view.isLayoutRtl()) {
+ view.right = (clockBounds.left - xBuffer).toInt()
+ view.left = view.right - viewWidth
+ } else {
+ view.left = (clockBounds.right + xBuffer).toInt()
+ view.right = view.left + viewWidth
}
}
}
@@ -218,11 +210,6 @@ object KeyguardSmartspaceViewBinder {
val dateView =
constraintLayout.requireViewById<View>(sharedR.id.date_smartspace_view)
addView(dateView)
- if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
- val weatherView =
- constraintLayout.requireViewById<View>(sharedR.id.weather_smartspace_view)
- addView(weatherView)
- }
}
}
}
@@ -240,11 +227,6 @@ object KeyguardSmartspaceViewBinder {
val dateView =
constraintLayout.requireViewById<View>(sharedR.id.date_smartspace_view)
removeView(dateView)
- if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
- val weatherView =
- constraintLayout.requireViewById<View>(sharedR.id.weather_smartspace_view)
- removeView(weatherView)
- }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index f717431f6a40..bca0bedc7350 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -39,7 +39,7 @@ import com.android.systemui.statusbar.notification.icon.ui.viewbinder.AlwaysOnDi
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerViewBinder
import com.android.systemui.statusbar.notification.icon.ui.viewbinder.StatusBarIconViewBindingFailureTracker
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
import com.android.systemui.statusbar.phone.NotificationIconContainer
import com.android.systemui.statusbar.ui.SystemBarUtilsState
import com.android.systemui.util.ui.value
@@ -102,7 +102,7 @@ constructor(
val isShadeLayoutWide = shadeModeInteractor.isShadeLayoutWide.value
constraintSet.apply {
- if (PromotedNotificationUiAod.isEnabled) {
+ if (PromotedNotificationUi.isEnabled) {
connect(nicId, TOP, AodPromotedNotificationSection.viewId, BOTTOM, bottomMargin)
} else {
connect(nicId, TOP, R.id.smart_space_barrier_bottom, BOTTOM, bottomMargin)
@@ -111,7 +111,7 @@ constructor(
setGoneMargin(nicId, BOTTOM, bottomMargin)
setVisibility(nicId, if (isVisible.value) VISIBLE else GONE)
- if (PromotedNotificationUiAod.isEnabled && isShadeLayoutWide) {
+ if (PromotedNotificationUi.isEnabled && isShadeLayoutWide) {
// Don't create a start constraint, so the icons can hopefully right-align.
} else {
connect(nicId, START, PARENT_ID, START, horizontalMargin)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt
index efdc5abf1f67..f75b53017500 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodPromotedNotificationSection.kt
@@ -31,7 +31,7 @@ import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.statusbar.notification.promoted.AODPromotedNotification
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationLogger
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
import com.android.systemui.statusbar.notification.promoted.ui.viewmodel.AODPromotedNotificationViewModel
import javax.inject.Inject
@@ -50,7 +50,7 @@ constructor(
}
override fun addViews(constraintLayout: ConstraintLayout) {
- if (!PromotedNotificationUiAod.isEnabled) {
+ if (!PromotedNotificationUi.isEnabled) {
return
}
@@ -67,7 +67,7 @@ constructor(
}
override fun bindData(constraintLayout: ConstraintLayout) {
- if (!PromotedNotificationUiAod.isEnabled) {
+ if (!PromotedNotificationUi.isEnabled) {
return
}
@@ -79,7 +79,7 @@ constructor(
}
override fun applyConstraints(constraintSet: ConstraintSet) {
- if (!PromotedNotificationUiAod.isEnabled) {
+ if (!PromotedNotificationUi.isEnabled) {
return
}
@@ -119,7 +119,7 @@ constructor(
}
override fun removeViews(constraintLayout: ConstraintLayout) {
- if (!PromotedNotificationUiAod.isEnabled) {
+ if (!PromotedNotificationUi.isEnabled) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index 8a33c6471326..9c6f46570b1d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -121,18 +121,22 @@ constructor(
setAlpha(getNonTargetClockFace(clock).views, 0F)
if (!keyguardClockViewModel.isLargeClockVisible.value) {
- if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
+ if (
+ KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(
+ context.resources.configuration
+ )
+ ) {
connect(
sharedR.id.bc_smartspace_view,
TOP,
- customR.id.lockscreen_clock_view,
+ sharedR.id.date_smartspace_view,
BOTTOM,
)
} else {
connect(
sharedR.id.bc_smartspace_view,
TOP,
- sharedR.id.date_smartspace_view,
+ customR.id.lockscreen_clock_view,
BOTTOM,
)
}
@@ -187,6 +191,8 @@ constructor(
val guideline =
if (keyguardClockViewModel.clockShouldBeCentered.value) PARENT_ID
else R.id.split_shade_guideline
+ val dateWeatherBelowSmallClock =
+ KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(context.resources.configuration)
constraints.apply {
connect(customR.id.lockscreen_clock_view_large, START, PARENT_ID, START)
connect(customR.id.lockscreen_clock_view_large, END, guideline, END)
@@ -254,11 +260,7 @@ constructor(
0
}
- if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
- clockInteractor.setNotificationStackDefaultTop(
- (smallClockBottom + marginBetweenSmartspaceAndNotification).toFloat()
- )
- } else {
+ if (dateWeatherBelowSmallClock) {
val dateWeatherSmartspaceHeight =
getDimen(context, DATE_WEATHER_VIEW_HEIGHT).toFloat()
clockInteractor.setNotificationStackDefaultTop(
@@ -266,6 +268,10 @@ constructor(
dateWeatherSmartspaceHeight +
marginBetweenSmartspaceAndNotification
)
+ } else {
+ clockInteractor.setNotificationStackDefaultTop(
+ (smallClockBottom + marginBetweenSmartspaceAndNotification).toFloat()
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index d0b5f743c277..d9652b590678 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -20,6 +20,7 @@ import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver.OnGlobalLayoutListener
+import android.widget.LinearLayout
import androidx.constraintlayout.widget.Barrier
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
@@ -57,10 +58,8 @@ constructor(
private val keyguardRootViewModel: KeyguardRootViewModel,
) : KeyguardSection() {
private var smartspaceView: View? = null
- private var weatherView: View? = null
private var dateView: ViewGroup? = null
- private var weatherViewLargeClock: View? = null
- private var dateViewLargeClock: View? = null
+ private var dateViewLargeClock: ViewGroup? = null
private var smartspaceVisibilityListener: OnGlobalLayoutListener? = null
private var pastVisibility: Int = -1
@@ -77,34 +76,47 @@ constructor(
override fun addViews(constraintLayout: ConstraintLayout) {
if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
smartspaceView = smartspaceController.buildAndConnectView(constraintLayout)
- weatherView = smartspaceController.buildAndConnectWeatherView(constraintLayout, false)
dateView =
smartspaceController.buildAndConnectDateView(constraintLayout, false) as? ViewGroup
+ var weatherViewLargeClock: View? = null
+ val weatherView: View? =
+ smartspaceController.buildAndConnectWeatherView(constraintLayout, false)
if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
weatherViewLargeClock =
smartspaceController.buildAndConnectWeatherView(constraintLayout, true)
dateViewLargeClock =
- smartspaceController.buildAndConnectDateView(constraintLayout, true)
+ smartspaceController.buildAndConnectDateView(constraintLayout, true) as? ViewGroup
}
pastVisibility = smartspaceView?.visibility ?: View.GONE
constraintLayout.addView(smartspaceView)
if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
dateView?.visibility = View.GONE
- weatherView?.visibility = View.GONE
dateViewLargeClock?.visibility = View.GONE
- weatherViewLargeClock?.visibility = View.GONE
- constraintLayout.addView(dateView)
- constraintLayout.addView(weatherView)
- constraintLayout.addView(weatherViewLargeClock)
constraintLayout.addView(dateViewLargeClock)
- } else {
if (keyguardSmartspaceViewModel.isDateWeatherDecoupled) {
- constraintLayout.addView(dateView)
// Place weather right after the date, before the extras (alarm and dnd)
- val index = if (dateView?.childCount == 0) 0 else 1
- dateView?.addView(weatherView, index)
+ val index = if (dateViewLargeClock?.childCount == 0) 0 else 1
+ dateViewLargeClock?.addView(weatherViewLargeClock, index)
+ }
+
+ if (
+ KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(
+ context.resources.configuration,
+ keyguardClockViewModel.hasCustomWeatherDataDisplay.value,
+ )
+ ) {
+ (dateView as? LinearLayout)?.orientation = LinearLayout.HORIZONTAL
+ } else {
+ (dateView as? LinearLayout)?.orientation = LinearLayout.VERTICAL
}
}
+
+ if (keyguardSmartspaceViewModel.isDateWeatherDecoupled) {
+ constraintLayout.addView(dateView)
+ // Place weather right after the date, before the extras (alarm and dnd)
+ val index = if (dateView?.childCount == 0) 0 else 1
+ dateView?.addView(weatherView, index)
+ }
keyguardUnlockAnimationController.lockscreenSmartspace = smartspaceView
smartspaceVisibilityListener = OnGlobalLayoutListener {
smartspaceView?.let {
@@ -136,10 +148,15 @@ constructor(
val dateWeatherPaddingStart = KeyguardSmartspaceViewModel.getDateWeatherStartMargin(context)
val smartspaceHorizontalPadding =
KeyguardSmartspaceViewModel.getSmartspaceHorizontalMargin(context)
+ val dateWeatherBelowSmallClock =
+ KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(
+ context.resources.configuration,
+ keyguardClockViewModel.hasCustomWeatherDataDisplay.value,
+ )
constraintSet.apply {
constrainHeight(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
constrainWidth(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
- if (!com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
+ if (dateWeatherBelowSmallClock) {
connect(
sharedR.id.date_smartspace_view,
ConstraintSet.START,
@@ -167,7 +184,7 @@ constructor(
smartspaceHorizontalPadding,
)
if (keyguardClockViewModel.hasCustomWeatherDataDisplay.value) {
- if (!com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
+ if (dateWeatherBelowSmallClock) {
clear(sharedR.id.date_smartspace_view, ConstraintSet.TOP)
connect(
sharedR.id.date_smartspace_view,
@@ -179,12 +196,27 @@ constructor(
} else {
clear(sharedR.id.date_smartspace_view, ConstraintSet.BOTTOM)
if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
- connect(
- sharedR.id.bc_smartspace_view,
- ConstraintSet.TOP,
- customR.id.lockscreen_clock_view,
- ConstraintSet.BOTTOM,
- )
+ if (dateWeatherBelowSmallClock) {
+ connect(
+ sharedR.id.date_smartspace_view,
+ ConstraintSet.TOP,
+ customR.id.lockscreen_clock_view,
+ ConstraintSet.BOTTOM,
+ )
+ connect(
+ sharedR.id.bc_smartspace_view,
+ ConstraintSet.TOP,
+ sharedR.id.date_smartspace_view,
+ ConstraintSet.BOTTOM,
+ )
+ } else {
+ connect(
+ sharedR.id.bc_smartspace_view,
+ ConstraintSet.TOP,
+ customR.id.lockscreen_clock_view,
+ ConstraintSet.BOTTOM,
+ )
+ }
} else {
connect(
sharedR.id.date_smartspace_view,
@@ -203,7 +235,6 @@ constructor(
if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
if (keyguardClockViewModel.isLargeClockVisible.value) {
- setVisibility(sharedR.id.weather_smartspace_view, GONE)
setVisibility(sharedR.id.date_smartspace_view, GONE)
constrainHeight(
sharedR.id.date_smartspace_view_large,
@@ -238,118 +269,79 @@ constructor(
connect(
sharedR.id.date_smartspace_view_large,
ConstraintSet.END,
- sharedR.id.weather_smartspace_view_large,
- ConstraintSet.START,
- )
-
- connect(
- sharedR.id.weather_smartspace_view_large,
- ConstraintSet.BOTTOM,
- sharedR.id.date_smartspace_view_large,
- ConstraintSet.BOTTOM,
- )
-
- connect(
- sharedR.id.weather_smartspace_view_large,
- ConstraintSet.TOP,
- sharedR.id.date_smartspace_view_large,
- ConstraintSet.TOP,
- )
-
- connect(
- sharedR.id.weather_smartspace_view_large,
- ConstraintSet.START,
- sharedR.id.date_smartspace_view_large,
- ConstraintSet.END,
- )
-
- connect(
- sharedR.id.weather_smartspace_view_large,
- ConstraintSet.END,
customR.id.lockscreen_clock_view_large,
ConstraintSet.END,
)
-
- setHorizontalChainStyle(
- sharedR.id.weather_smartspace_view_large,
- ConstraintSet.CHAIN_PACKED,
- )
setHorizontalChainStyle(
sharedR.id.date_smartspace_view_large,
ConstraintSet.CHAIN_PACKED,
)
} else {
- setVisibility(sharedR.id.weather_smartspace_view_large, GONE)
- setVisibility(sharedR.id.date_smartspace_view_large, GONE)
- constrainHeight(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
- constrainWidth(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
- constrainHeight(sharedR.id.weather_smartspace_view, ConstraintSet.WRAP_CONTENT)
- constrainWidth(sharedR.id.weather_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ if (dateWeatherBelowSmallClock) {
+ connect(
+ sharedR.id.date_smartspace_view,
+ ConstraintSet.START,
+ ConstraintSet.PARENT_ID,
+ ConstraintSet.START,
+ dateWeatherPaddingStart,
+ )
+ } else {
+ setVisibility(sharedR.id.date_smartspace_view_large, GONE)
+ constrainHeight(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ constrainWidth(sharedR.id.date_smartspace_view, ConstraintSet.WRAP_CONTENT)
+ connect(
+ sharedR.id.date_smartspace_view,
+ ConstraintSet.START,
+ customR.id.lockscreen_clock_view,
+ ConstraintSet.END,
+ context.resources.getDimensionPixelSize(
+ R.dimen.smartspace_padding_horizontal
+ ),
+ )
+ connect(
+ sharedR.id.date_smartspace_view,
+ ConstraintSet.TOP,
+ customR.id.lockscreen_clock_view,
+ ConstraintSet.TOP,
+ )
+ connect(
+ sharedR.id.date_smartspace_view,
+ ConstraintSet.BOTTOM,
+ customR.id.lockscreen_clock_view,
+ ConstraintSet.BOTTOM,
+ )
+ }
+ }
+ }
- connect(
- sharedR.id.date_smartspace_view,
- ConstraintSet.START,
- customR.id.lockscreen_clock_view,
- ConstraintSet.END,
- context.resources.getDimensionPixelSize(
- R.dimen.smartspace_padding_horizontal
- ),
- )
- connect(
- sharedR.id.date_smartspace_view,
- ConstraintSet.TOP,
- customR.id.lockscreen_clock_view,
- ConstraintSet.TOP,
- )
- connect(
- sharedR.id.date_smartspace_view,
- ConstraintSet.BOTTOM,
- sharedR.id.weather_smartspace_view,
- ConstraintSet.TOP,
- )
- connect(
- sharedR.id.weather_smartspace_view,
- ConstraintSet.START,
- sharedR.id.date_smartspace_view,
- ConstraintSet.START,
- )
- connect(
- sharedR.id.weather_smartspace_view,
- ConstraintSet.TOP,
- sharedR.id.date_smartspace_view,
- ConstraintSet.BOTTOM,
+ if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
+ if (dateWeatherBelowSmallClock) {
+ createBarrier(
+ R.id.smart_space_barrier_bottom,
+ Barrier.BOTTOM,
+ 0,
+ *intArrayOf(sharedR.id.bc_smartspace_view, sharedR.id.date_smartspace_view),
)
- connect(
- sharedR.id.weather_smartspace_view,
- ConstraintSet.BOTTOM,
- customR.id.lockscreen_clock_view,
- ConstraintSet.BOTTOM,
+ createBarrier(
+ R.id.smart_space_barrier_top,
+ Barrier.TOP,
+ 0,
+ *intArrayOf(sharedR.id.bc_smartspace_view, sharedR.id.date_smartspace_view),
)
-
- setVerticalChainStyle(
- sharedR.id.weather_smartspace_view,
- ConstraintSet.CHAIN_PACKED,
+ } else {
+ createBarrier(
+ R.id.smart_space_barrier_bottom,
+ Barrier.BOTTOM,
+ 0,
+ sharedR.id.bc_smartspace_view,
)
- setVerticalChainStyle(
- sharedR.id.date_smartspace_view,
- ConstraintSet.CHAIN_PACKED,
+ createBarrier(
+ R.id.smart_space_barrier_top,
+ Barrier.TOP,
+ 0,
+ sharedR.id.bc_smartspace_view,
)
}
- }
-
- if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
- createBarrier(
- R.id.smart_space_barrier_bottom,
- Barrier.BOTTOM,
- 0,
- sharedR.id.bc_smartspace_view,
- )
- createBarrier(
- R.id.smart_space_barrier_top,
- Barrier.TOP,
- 0,
- sharedR.id.bc_smartspace_view,
- )
} else {
createBarrier(
R.id.smart_space_barrier_bottom,
@@ -373,13 +365,7 @@ constructor(
val list =
if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
- listOf(
- smartspaceView,
- dateView,
- weatherView,
- weatherViewLargeClock,
- dateViewLargeClock,
- )
+ listOf(smartspaceView, dateView, dateViewLargeClock)
} else {
listOf(smartspaceView, dateView)
}
@@ -424,10 +410,8 @@ constructor(
if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
if (keyguardClockViewModel.isLargeClockVisible.value) {
- setVisibility(sharedR.id.weather_smartspace_view, GONE)
setVisibility(sharedR.id.date_smartspace_view, GONE)
} else {
- setVisibility(sharedR.id.weather_smartspace_view_large, GONE)
setVisibility(sharedR.id.date_smartspace_view_large, GONE)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
index 434d7eadd742..d830a8456d66 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/ClockSizeTransition.kt
@@ -299,14 +299,12 @@ class ClockSizeTransition(
}
if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
addTarget(sharedR.id.date_smartspace_view_large)
- addTarget(sharedR.id.weather_smartspace_view_large)
}
} else {
logger.i("Adding small clock")
addTarget(customR.id.lockscreen_clock_view)
- if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
+ if (!viewModel.dateWeatherBelowSmallClock()) {
addTarget(sharedR.id.date_smartspace_view)
- addTarget(sharedR.id.weather_smartspace_view)
}
}
}
@@ -386,7 +384,7 @@ class ClockSizeTransition(
duration =
if (isLargeClock) STATUS_AREA_MOVE_UP_MILLIS else STATUS_AREA_MOVE_DOWN_MILLIS
interpolator = Interpolators.EMPHASIZED
- if (!com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
+ if (viewModel.dateWeatherBelowSmallClock()) {
addTarget(sharedR.id.date_smartspace_view)
}
addTarget(sharedR.id.bc_smartspace_view)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt
index 0874b6da180e..9faca7567279 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/transitions/DefaultClockSteppingTransition.kt
@@ -32,7 +32,6 @@ class DefaultClockSteppingTransition(private val clock: ClockController) : Trans
addTarget(clock.largeClock.view)
if (com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout()) {
addTarget(sharedR.id.date_smartspace_view_large)
- addTarget(sharedR.id.weather_smartspace_view_large)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index dcbf7b5a9335..cf6845354f44 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -180,6 +180,9 @@ constructor(
val largeClockTextSize: Flow<Int> =
configurationInteractor.dimensionPixelSize(customR.dimen.large_clock_text_size)
+ fun dateWeatherBelowSmallClock() =
+ KeyguardSmartspaceViewModel.dateWeatherBelowSmallClock(context.resources.configuration)
+
enum class ClockLayout {
LARGE_CLOCK,
SMALL_CLOCK,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
index 5cc34e749b46..a00d0ced2c07 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
@@ -17,6 +17,8 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.content.Context
+import android.content.res.Configuration
+import android.util.Log
import com.android.systemui.customization.R as customR
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -94,6 +96,43 @@ constructor(
val isShadeLayoutWide: StateFlow<Boolean> = shadeModeInteractor.isShadeLayoutWide
companion object {
+ private const val TAG = "KeyguardSmartspaceVM"
+
+ fun dateWeatherBelowSmallClock(
+ configuration: Configuration,
+ customDateWeather: Boolean = false,
+ ): Boolean {
+ return if (
+ com.android.systemui.shared.Flags.clockReactiveSmartspaceLayout() &&
+ !customDateWeather
+ ) {
+ // font size to display size
+ // These values come from changing the font size and display size on a non-foldable.
+ // Visually looked at which configs cause the date/weather to push off of the screen
+ val breakingPairs =
+ listOf(
+ 0.85f to 320, // tiny font size but large display size
+ 1f to 346,
+ 1.15f to 346,
+ 1.5f to 376,
+ 1.8f to 411, // large font size but tiny display size
+ )
+ val screenWidthDp = configuration.screenWidthDp
+ val fontScale = configuration.fontScale
+ var fallBelow = false
+ for ((font, width) in breakingPairs) {
+ if (fontScale >= font && screenWidthDp <= width) {
+ fallBelow = true
+ break
+ }
+ }
+ Log.d(TAG, "Width: $screenWidthDp, Font: $fontScale, BelowClock: $fallBelow")
+ return fallBelow
+ } else {
+ true
+ }
+ }
+
fun getDateWeatherStartMargin(context: Context): Int {
return context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start) +
context.resources.getDimensionPixelSize(customR.dimen.status_view_margin_horizontal)
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
index bf1f971c0f8c..4f86257e3870 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaSwitchingController.java
@@ -609,8 +609,7 @@ public class MediaSwitchingController
devices,
getSelectedMediaDevice(),
connectedMediaDevice,
- needToHandleMutingExpectedDevice,
- getConnectNewDeviceItem());
+ needToHandleMutingExpectedDevice);
} else {
List<MediaItem> updatedMediaItems =
buildMediaItems(
@@ -701,7 +700,6 @@ public class MediaSwitchingController
}
}
dividerItems.forEach(finalMediaItems::add);
- attachConnectNewDeviceItemIfNeeded(finalMediaItems);
return finalMediaItems;
}
}
@@ -765,7 +763,6 @@ public class MediaSwitchingController
finalMediaItems.add(MediaItem.createDeviceMediaItem(device));
}
}
- attachConnectNewDeviceItemIfNeeded(finalMediaItems);
return finalMediaItems;
}
@@ -879,6 +876,15 @@ public class MediaSwitchingController
});
}
+ private List<MediaItem> getOutputDeviceList(boolean addConnectDeviceButton) {
+ List<MediaItem> mediaItems = new ArrayList<>(
+ mOutputMediaItemListProxy.getOutputMediaItemList());
+ if (addConnectDeviceButton) {
+ attachConnectNewDeviceItemIfNeeded(mediaItems);
+ }
+ return mediaItems;
+ }
+
private void addInputDevices(List<MediaItem> mediaItems) {
mediaItems.add(
MediaItem.createGroupDividerMediaItem(
@@ -886,22 +892,34 @@ public class MediaSwitchingController
mediaItems.addAll(mInputMediaItemList);
}
- private void addOutputDevices(List<MediaItem> mediaItems) {
+ private void addOutputDevices(List<MediaItem> mediaItems, boolean addConnectDeviceButton) {
mediaItems.add(
MediaItem.createGroupDividerMediaItem(
mContext.getString(R.string.media_output_group_title)));
- mediaItems.addAll(mOutputMediaItemListProxy.getOutputMediaItemList());
+ mediaItems.addAll(getOutputDeviceList(addConnectDeviceButton));
}
+ /**
+ * Returns a list of media items to be rendered in the device list. For backward compatibility
+ * reasons, adds a "Connect a device" button by default.
+ */
public List<MediaItem> getMediaItemList() {
+ return getMediaItemList(true /* addConnectDeviceButton */);
+ }
+
+ /**
+ * Returns a list of media items to be rendered in the device list.
+ * @param addConnectDeviceButton Whether to add a "Connect a device" button to the list.
+ */
+ public List<MediaItem> getMediaItemList(boolean addConnectDeviceButton) {
// If input routing is not enabled, only return output media items.
if (!enableInputRouting()) {
- return mOutputMediaItemListProxy.getOutputMediaItemList();
+ return getOutputDeviceList(addConnectDeviceButton);
}
// If input routing is enabled, return both output and input media items.
List<MediaItem> mediaItems = new ArrayList<>();
- addOutputDevices(mediaItems);
+ addOutputDevices(mediaItems, addConnectDeviceButton);
addInputDevices(mediaItems);
return mediaItems;
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java b/packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java
index 45ca2c6ee8e5..c15ef82f0378 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/OutputMediaItemListProxy.java
@@ -44,7 +44,6 @@ public class OutputMediaItemListProxy {
private final List<MediaItem> mSelectedMediaItems;
private final List<MediaItem> mSuggestedMediaItems;
private final List<MediaItem> mSpeakersAndDisplaysMediaItems;
- @Nullable private MediaItem mConnectNewDeviceMediaItem;
public OutputMediaItemListProxy(Context context) {
mContext = context;
@@ -88,9 +87,6 @@ public class OutputMediaItemListProxy {
R.string.media_output_group_title_speakers_and_displays)));
finalMediaItems.addAll(mSpeakersAndDisplaysMediaItems);
}
- if (mConnectNewDeviceMediaItem != null) {
- finalMediaItems.add(mConnectNewDeviceMediaItem);
- }
return finalMediaItems;
}
@@ -99,8 +95,7 @@ public class OutputMediaItemListProxy {
List<MediaDevice> devices,
List<MediaDevice> selectedDevices,
@Nullable MediaDevice connectedMediaDevice,
- boolean needToHandleMutingExpectedDevice,
- @Nullable MediaItem connectNewDeviceMediaItem) {
+ boolean needToHandleMutingExpectedDevice) {
Set<String> selectedOrConnectedMediaDeviceIds =
selectedDevices.stream().map(MediaDevice::getId).collect(Collectors.toSet());
if (connectedMediaDevice != null) {
@@ -177,7 +172,6 @@ public class OutputMediaItemListProxy {
mSuggestedMediaItems.addAll(updatedSuggestedMediaItems);
mSpeakersAndDisplaysMediaItems.clear();
mSpeakersAndDisplaysMediaItems.addAll(updatedSpeakersAndDisplaysMediaItems);
- mConnectNewDeviceMediaItem = connectNewDeviceMediaItem;
// The cached mOutputMediaItemList is cleared upon any update to individual media item
// lists. This ensures getOutputMediaItemList() computes and caches a fresh list on the next
@@ -197,10 +191,6 @@ public class OutputMediaItemListProxy {
mSelectedMediaItems.removeIf((MediaItem::isMutingExpectedDevice));
mSuggestedMediaItems.removeIf((MediaItem::isMutingExpectedDevice));
mSpeakersAndDisplaysMediaItems.removeIf((MediaItem::isMutingExpectedDevice));
- if (mConnectNewDeviceMediaItem != null
- && mConnectNewDeviceMediaItem.isMutingExpectedDevice()) {
- mConnectNewDeviceMediaItem = null;
- }
}
mOutputMediaItemList.removeIf((MediaItem::isMutingExpectedDevice));
}
@@ -211,7 +201,6 @@ public class OutputMediaItemListProxy {
mSelectedMediaItems.clear();
mSuggestedMediaItems.clear();
mSpeakersAndDisplaysMediaItems.clear();
- mConnectNewDeviceMediaItem = null;
}
mOutputMediaItemList.clear();
}
@@ -221,8 +210,7 @@ public class OutputMediaItemListProxy {
if (Flags.fixOutputMediaItemListIndexOutOfBoundsException()) {
return mSelectedMediaItems.isEmpty()
&& mSuggestedMediaItems.isEmpty()
- && mSpeakersAndDisplaysMediaItems.isEmpty()
- && (mConnectNewDeviceMediaItem == null);
+ && mSpeakersAndDisplaysMediaItems.isEmpty();
} else {
return mOutputMediaItemList.isEmpty();
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionUtils.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionUtils.kt
index 88cbc3867744..a8d0e0573d89 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionUtils.kt
@@ -18,6 +18,7 @@ package com.android.systemui.mediaprojection.permission
import android.content.Context
import android.media.projection.MediaProjectionConfig
+import com.android.media.projection.flags.Flags
import com.android.systemui.res.R
/** Various utility methods related to media projection permissions. */
@@ -28,13 +29,27 @@ object MediaProjectionPermissionUtils {
mediaProjectionConfig: MediaProjectionConfig?,
overrideDisableSingleAppOption: Boolean,
): String? {
- // The single app option should only be disabled if the client has setup a
- // MediaProjection with MediaProjectionConfig#createConfigForDefaultDisplay AND
- // it hasn't been overridden by the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override.
+
val singleAppOptionDisabled =
!overrideDisableSingleAppOption &&
- mediaProjectionConfig?.regionToCapture ==
- MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
+ if (Flags.appContentSharing()) {
+ // The single app option should only be disabled if the client has setup a
+ // MediaProjection with MediaProjection.isChoiceAppEnabled == false (e.g by
+ // creating it
+ // with MediaProjectionConfig#createConfigForDefaultDisplay AND
+ // it hasn't been overridden by the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app
+ // override.
+ mediaProjectionConfig?.isSourceEnabled(
+ MediaProjectionConfig.PROJECTION_SOURCE_APP
+ ) == false
+ } else {
+ // The single app option should only be disabled if the client has setup a
+ // MediaProjection with MediaProjectionConfig#createConfigForDefaultDisplay AND
+ // it hasn't been overridden by the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app
+ // override.
+ mediaProjectionConfig?.regionToCapture ==
+ MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY
+ }
return if (singleAppOptionDisabled) {
context.getString(
R.string.media_projection_entry_app_permission_dialog_single_app_disabled,
diff --git a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
index 465c78e91e53..2a7fb5467173 100644
--- a/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/notifications/ui/viewmodel/NotificationsShadeOverlayContentViewModel.kt
@@ -23,17 +23,14 @@ import com.android.systemui.lifecycle.Hydrator
import com.android.systemui.media.controls.domain.pipeline.interactor.MediaCarouselInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.shade.domain.interactor.ShadeInteractor
-import com.android.systemui.shade.domain.interactor.ShadeModeInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel
import com.android.systemui.statusbar.disableflags.domain.interactor.DisableFlagsInteractor
-import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.coroutineScope
-import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flowOf
@@ -51,31 +48,12 @@ constructor(
val notificationsPlaceholderViewModelFactory: NotificationsPlaceholderViewModel.Factory,
val sceneInteractor: SceneInteractor,
private val shadeInteractor: ShadeInteractor,
- shadeModeInteractor: ShadeModeInteractor,
disableFlagsInteractor: DisableFlagsInteractor,
mediaCarouselInteractor: MediaCarouselInteractor,
- activeNotificationsInteractor: ActiveNotificationsInteractor,
) : ExclusiveActivatable() {
private val hydrator = Hydrator("NotificationsShadeOverlayContentViewModel.hydrator")
- val showClock: Boolean by
- hydrator.hydratedStateOf(
- traceName = "showClock",
- initialValue =
- shouldShowClock(
- isShadeLayoutWide = shadeModeInteractor.isShadeLayoutWide.value,
- areAnyNotificationsPresent =
- activeNotificationsInteractor.areAnyNotificationsPresentValue,
- ),
- source =
- combine(
- shadeModeInteractor.isShadeLayoutWide,
- activeNotificationsInteractor.areAnyNotificationsPresent,
- this::shouldShowClock,
- ),
- )
-
val showMedia: Boolean by
hydrator.hydratedStateOf(
traceName = "showMedia",
@@ -114,13 +92,6 @@ constructor(
shadeInteractor.collapseNotificationsShade(loggingReason = "shade scrim clicked")
}
- private fun shouldShowClock(
- isShadeLayoutWide: Boolean,
- areAnyNotificationsPresent: Boolean,
- ): Boolean {
- return !isShadeLayoutWide && areAnyNotificationsPresent
- }
-
@AssistedFactory
interface Factory {
fun create(): NotificationsShadeOverlayContentViewModel
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
index 1a0af514cf87..bd7e7832751a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt
@@ -290,6 +290,8 @@ private fun TileLabel(
) {
var textSize by remember { mutableIntStateOf(0) }
+ val iterations = if (isVisible()) TILE_MARQUEE_ITERATIONS else 0
+
BasicText(
text = text,
color = color,
@@ -322,14 +324,10 @@ private fun TileLabel(
)
}
}
- .thenIf(isVisible()) {
- // Only apply the marquee when the label is visible, which is needed for the
- // always composed QS
- Modifier.basicMarquee(
- iterations = TILE_MARQUEE_ITERATIONS,
- initialDelayMillis = TILE_INITIAL_DELAY_MILLIS,
- )
- },
+ .basicMarquee(
+ iterations = iterations,
+ initialDelayMillis = TILE_INITIAL_DELAY_MILLIS,
+ ),
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
index b21c3e4e44e1..6236fff87f63 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetAdapter.java
@@ -196,11 +196,16 @@ public class InternetAdapter extends RecyclerView.Adapter<InternetAdapter.Intern
if (mJob == null) {
mJob = WifiUtils.checkWepAllowed(mContext, mCoroutineScope, wifiEntry.getSsid(),
WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, intent -> {
- mInternetDetailsContentController.startActivityForDialog(intent);
+ mInternetDetailsContentController
+ .startActivityForDialog(intent);
return null;
}, () -> {
wifiConnect(wifiEntry, view);
return null;
+ }, intent -> {
+ mInternetDetailsContentController
+ .startActivityForDialogDismissDialogFirst(intent, view);
+ return null;
});
}
return;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java
index 945e051606b9..2497daebdd6d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDetailsContentController.java
@@ -784,6 +784,17 @@ public class InternetDetailsContentController implements AccessPointController.A
mActivityStarter.startActivity(intent, false /* dismissShade */);
}
+ // Closes the dialog first, as the WEP dialog is in a different process and can have weird
+ // interactions otherwise.
+ void startActivityForDialogDismissDialogFirst(Intent intent, View view) {
+ ActivityTransitionAnimator.Controller controller =
+ mDialogTransitionAnimator.createActivityTransitionController(view);
+ if (mCallback != null) {
+ mCallback.dismissDialog();
+ }
+ mActivityStarter.startActivity(intent, false /* dismissShade */, controller);
+ }
+
void launchNetworkSetting(View view) {
startActivity(getSettingsIntent(), view);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java
index f4c77da674b0..742067a98057 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/scroll/ScrollCaptureController.java
@@ -24,6 +24,8 @@ import android.provider.Settings;
import android.util.Log;
import android.view.ScrollCaptureResponse;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.concurrent.futures.CallbackToFutureAdapter.Completer;
@@ -68,11 +70,15 @@ public class ScrollCaptureController {
private final UiEventLogger mEventLogger;
private final ScrollCaptureClient mClient;
+ @Nullable
private Completer<LongScreenshot> mCaptureCompleter;
+ @Nullable
private ListenableFuture<Session> mSessionFuture;
private Session mSession;
+ @Nullable
private ListenableFuture<CaptureResult> mTileFuture;
+ @Nullable
private ListenableFuture<Void> mEndFuture;
private String mWindowOwner;
private volatile boolean mCancelled;
@@ -148,8 +154,9 @@ public class ScrollCaptureController {
}
@Inject
- ScrollCaptureController(Context context, @Background Executor bgExecutor,
- ScrollCaptureClient client, ImageTileSet imageTileSet, UiEventLogger logger) {
+ ScrollCaptureController(@NonNull Context context, @Background Executor bgExecutor,
+ @NonNull ScrollCaptureClient client, @NonNull ImageTileSet imageTileSet,
+ @NonNull UiEventLogger logger) {
mContext = context;
mBgExecutor = bgExecutor;
mClient = client;
@@ -214,7 +221,9 @@ public class ScrollCaptureController {
} catch (InterruptedException | ExecutionException e) {
// Failure to start, propagate to caller
Log.e(TAG, "session start failed!");
- mCaptureCompleter.setException(e);
+ if (mCaptureCompleter != null) {
+ mCaptureCompleter.setException(e);
+ }
mEventLogger.log(ScreenshotEvent.SCREENSHOT_LONG_SCREENSHOT_FAILURE, 0, mWindowOwner);
}
}
@@ -235,7 +244,9 @@ public class ScrollCaptureController {
Log.e(TAG, "requestTile cancelled");
} catch (InterruptedException | ExecutionException e) {
Log.e(TAG, "requestTile failed!", e);
- mCaptureCompleter.setException(e);
+ if (mCaptureCompleter != null) {
+ mCaptureCompleter.setException(e);
+ }
}
}, mBgExecutor);
}
@@ -350,7 +361,9 @@ public class ScrollCaptureController {
}
// Provide result to caller and complete the top-level future
// Caller is responsible for releasing this resource (ImageReader/HardwareBuffers)
- mCaptureCompleter.set(new LongScreenshot(mSession, mImageTileSet));
+ if (mCaptureCompleter != null) {
+ mCaptureCompleter.set(new LongScreenshot(mSession, mImageTileSet));
+ }
}, mContext.getMainExecutor());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index c800ab3d0bf2..913aacb53e12 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -20,7 +20,6 @@ import android.content.Context
import android.content.res.Configuration
import android.graphics.Rect
import android.os.PowerManager
-import android.os.SystemClock
import android.util.ArraySet
import android.view.GestureDetector
import android.view.MotionEvent
@@ -54,6 +53,7 @@ import com.android.systemui.communal.ui.compose.CommunalContainer
import com.android.systemui.communal.ui.compose.CommunalContent
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.util.CommunalColors
+import com.android.systemui.communal.util.UserTouchActivityNotifier
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -101,6 +101,7 @@ constructor(
private val notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
private val keyguardMediaController: KeyguardMediaController,
private val lockscreenSmartspaceController: LockscreenSmartspaceController,
+ private val userTouchActivityNotifier: UserTouchActivityNotifier,
@CommunalTouchLog logBuffer: LogBuffer,
private val userActivityNotifier: UserActivityNotifier,
) : LifecycleOwner {
@@ -646,8 +647,8 @@ constructor(
// result in broken states.
return true
}
+ var handled = hubShowing
try {
- var handled = false
if (!touchTakenByKeyguardGesture) {
communalContainerWrapper?.dispatchTouchEvent(ev) {
if (it) {
@@ -655,18 +656,10 @@ constructor(
}
}
}
- return handled || hubShowing
+ return handled
} finally {
- if (Flags.bouncerUiRevamp()) {
- userActivityNotifier.notifyUserActivity(
- event = PowerManager.USER_ACTIVITY_EVENT_TOUCH
- )
- } else {
- powerManager.userActivity(
- SystemClock.uptimeMillis(),
- PowerManager.USER_ACTIVITY_EVENT_TOUCH,
- 0,
- )
+ if (handled) {
+ userTouchActivityNotifier.notifyActivity(ev)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
index 9282e166f605..2238db505948 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/AODPromotedNotification.kt
@@ -81,7 +81,7 @@ fun AODPromotedNotification(
viewModelFactory: AODPromotedNotificationViewModel.Factory,
modifier: Modifier = Modifier,
) {
- if (!PromotedNotificationUiAod.isEnabled) {
+ if (!PromotedNotificationUi.isEnabled) {
return
}
@@ -170,24 +170,35 @@ private class FrameLayoutWithMaxHeight(maxHeight: Int, context: Context) : Frame
// This mirrors the logic in NotificationContentView.onMeasure.
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
- if (childCount < 1) {
- return
+ if (childCount != 1) {
+ Log.wtf(TAG, "Should contain exactly one child.")
+ return super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
- val child = getChildAt(0)
- val childLayoutHeight = child.layoutParams.height
- val childHeightSpec =
- if (childLayoutHeight >= 0) {
- makeMeasureSpec(maxHeight.coerceAtMost(childLayoutHeight), EXACTLY)
- } else {
- makeMeasureSpec(maxHeight, AT_MOST)
- }
- measureChildWithMargins(child, widthMeasureSpec, 0, childHeightSpec, 0)
- val childMeasuredHeight = child.measuredHeight
+ val horizPadding = paddingStart + paddingEnd
+ val vertPadding = paddingTop + paddingBottom
+ val ownWidthSize = MeasureSpec.getSize(widthMeasureSpec)
val ownHeightMode = MeasureSpec.getMode(heightMeasureSpec)
val ownHeightSize = MeasureSpec.getSize(heightMeasureSpec)
+ val availableHeight =
+ if (ownHeightMode != UNSPECIFIED) {
+ maxHeight.coerceAtMost(ownHeightSize)
+ } else {
+ maxHeight
+ }
+
+ val child = getChildAt(0)
+ val childWidthSpec = makeMeasureSpec(ownWidthSize, EXACTLY)
+ val childHeightSpec =
+ child.layoutParams.height
+ .takeIf { it >= 0 }
+ ?.let { makeMeasureSpec(availableHeight.coerceAtMost(it), EXACTLY) }
+ ?: run { makeMeasureSpec(availableHeight, AT_MOST) }
+ measureChildWithMargins(child, childWidthSpec, horizPadding, childHeightSpec, vertPadding)
+ val childMeasuredHeight = child.measuredHeight
+
val ownMeasuredWidth = MeasureSpec.getSize(widthMeasureSpec)
val ownMeasuredHeight =
if (ownHeightMode != UNSPECIFIED) {
@@ -195,7 +206,6 @@ private class FrameLayoutWithMaxHeight(maxHeight: Int, context: Context) : Frame
} else {
childMeasuredHeight
}
-
setMeasuredDimension(ownMeasuredWidth, ownMeasuredHeight)
}
}
@@ -205,18 +215,22 @@ private val PromotedNotificationContentModel.layoutResource: Int?
return if (notificationsRedesignTemplates()) {
when (style) {
Style.Base -> R.layout.notification_2025_template_expanded_base
+ Style.CollapsedBase -> R.layout.notification_2025_template_collapsed_base
Style.BigPicture -> R.layout.notification_2025_template_expanded_big_picture
Style.BigText -> R.layout.notification_2025_template_expanded_big_text
Style.Call -> R.layout.notification_2025_template_expanded_call
+ Style.CollapsedCall -> R.layout.notification_2025_template_collapsed_call
Style.Progress -> R.layout.notification_2025_template_expanded_progress
Style.Ineligible -> null
}
} else {
when (style) {
Style.Base -> R.layout.notification_template_material_big_base
+ Style.CollapsedBase -> R.layout.notification_template_material_base
Style.BigPicture -> R.layout.notification_template_material_big_picture
Style.BigText -> R.layout.notification_template_material_big_text
Style.Call -> R.layout.notification_template_material_big_call
+ Style.CollapsedCall -> R.layout.notification_template_material_call
Style.Progress -> R.layout.notification_template_material_progress
Style.Ineligible -> null
}
@@ -333,10 +347,12 @@ private class AODPromotedNotificationViewUpdater(root: View) {
fun update(content: PromotedNotificationContentModel, audiblyAlertedIconVisible: Boolean) {
when (content.style) {
- Style.Base -> updateBase(content)
+ Style.Base -> updateBase(content, collapsed = false)
+ Style.CollapsedBase -> updateBase(content, collapsed = true)
Style.BigPicture -> updateBigPictureStyle(content)
Style.BigText -> updateBigTextStyle(content)
- Style.Call -> updateCallStyle(content)
+ Style.Call -> updateCallStyle(content, collapsed = false)
+ Style.CollapsedCall -> updateCallStyle(content, collapsed = true)
Style.Progress -> updateProgressStyle(content)
Style.Ineligible -> {}
}
@@ -346,11 +362,15 @@ private class AODPromotedNotificationViewUpdater(root: View) {
private fun updateBase(
content: PromotedNotificationContentModel,
+ collapsed: Boolean,
textView: ImageFloatingTextView? = text,
) {
- updateHeader(content)
+ val headerTitleView = if (collapsed) title else null
+ updateHeader(content, titleView = headerTitleView, collapsed = collapsed)
- updateTitle(title, content)
+ if (headerTitleView == null) {
+ updateTitle(title, content)
+ }
updateText(textView, content)
updateSmallIcon(icon, content)
updateImageView(rightIcon, content.skeletonLargeIcon)
@@ -358,21 +378,21 @@ private class AODPromotedNotificationViewUpdater(root: View) {
}
private fun updateBigPictureStyle(content: PromotedNotificationContentModel) {
- updateBase(content)
+ updateBase(content, collapsed = false)
}
private fun updateBigTextStyle(content: PromotedNotificationContentModel) {
- updateBase(content, textView = bigText)
+ updateBase(content, collapsed = false, textView = bigText)
}
- private fun updateCallStyle(content: PromotedNotificationContentModel) {
- updateConversationHeader(content)
+ private fun updateCallStyle(content: PromotedNotificationContentModel, collapsed: Boolean) {
+ updateConversationHeader(content, collapsed = collapsed)
updateText(text, content)
}
private fun updateProgressStyle(content: PromotedNotificationContentModel) {
- updateBase(content)
+ updateBase(content, collapsed = false)
updateNewProgressBar(content)
}
@@ -409,24 +429,35 @@ private class AODPromotedNotificationViewUpdater(root: View) {
}
}
- private fun updateHeader(content: PromotedNotificationContentModel) {
- updateAppName(content)
+ private fun updateHeader(
+ content: PromotedNotificationContentModel,
+ collapsed: Boolean,
+ titleView: TextView?,
+ ) {
+ val hasTitle = titleView != null && content.title != null
+ val hasSubText = content.subText != null
+ // the collapsed form doesn't show the app name unless there is no other text in the header
+ val appNameRequired = !hasTitle && !hasSubText
+ val hideAppName = (!appNameRequired && collapsed)
+
+ updateAppName(content, forceHide = hideAppName)
updateTextView(headerTextSecondary, content.subText)
- // Not calling updateTitle(headerText, content) because the title is always a separate
- // element in the expanded layout used for AOD RONs.
+ updateTitle(titleView, content)
updateTimeAndChronometer(content)
- updateHeaderDividers(content)
+ updateHeaderDividers(content, hideTitle = !hasTitle, hideAppName = hideAppName)
updateTopLine(content)
}
- private fun updateHeaderDividers(content: PromotedNotificationContentModel) {
- val hasAppName = content.appName != null
+ private fun updateHeaderDividers(
+ content: PromotedNotificationContentModel,
+ hideAppName: Boolean,
+ hideTitle: Boolean,
+ ) {
+ val hasAppName = content.appName != null && !hideAppName
val hasSubText = content.subText != null
- // Not setting hasHeader = content.title because the title is always a separate element in
- // the expanded layout used for AOD RONs.
- val hasHeader = false
+ val hasHeader = content.title != null && !hideTitle
val hasTimeOrChronometer = content.time != null
val hasTextBeforeSubText = hasAppName
@@ -442,13 +473,17 @@ private class AODPromotedNotificationViewUpdater(root: View) {
timeDivider?.isVisible = showDividerBeforeTime
}
- private fun updateConversationHeader(content: PromotedNotificationContentModel) {
- updateAppName(content)
+ private fun updateConversationHeader(
+ content: PromotedNotificationContentModel,
+ collapsed: Boolean,
+ ) {
+ updateAppName(content, forceHide = collapsed)
updateTimeAndChronometer(content)
+
updateImageView(verificationIcon, content.verificationIcon)
updateTextView(verificationText, content.verificationText)
- updateConversationHeaderDividers(content)
+ updateConversationHeaderDividers(content, hideTitle = true, hideAppName = collapsed)
updateTopLine(content)
@@ -456,11 +491,13 @@ private class AODPromotedNotificationViewUpdater(root: View) {
updateTitle(conversationText, content)
}
- private fun updateConversationHeaderDividers(content: PromotedNotificationContentModel) {
- // Not setting hasTitle = content.title because the title is always a separate element in
- // the expanded layout used for AOD RONs.
- val hasTitle = false
- val hasAppName = content.appName != null
+ private fun updateConversationHeaderDividers(
+ content: PromotedNotificationContentModel,
+ hideTitle: Boolean,
+ hideAppName: Boolean,
+ ) {
+ val hasTitle = content.title != null && !hideTitle
+ val hasAppName = content.appName != null && !hideAppName
val hasTimeOrChronometer = content.time != null
val hasVerification =
!content.verificationIcon.isNullOrEmpty() || content.verificationText != null
@@ -478,8 +515,8 @@ private class AODPromotedNotificationViewUpdater(root: View) {
verificationDivider?.isVisible = showDividerBeforeVerification
}
- private fun updateAppName(content: PromotedNotificationContentModel) {
- updateTextView(appNameText, content.appName)
+ private fun updateAppName(content: PromotedNotificationContentModel, forceHide: Boolean) {
+ updateTextView(appNameText, content.appName?.takeUnless { forceHide })
}
private fun updateTitle(titleView: TextView?, content: PromotedNotificationContentModel) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
index d9bdfbc81145..9fe3ff4c4bce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractor.kt
@@ -112,12 +112,13 @@ constructor(
if (redactionType == REDACTION_TYPE_NONE) {
privateVersion
} else {
- if (notification.publicVersion == null) {
- privateVersion.toDefaultPublicVersion()
- } else {
- // TODO(b/400991304): implement extraction for [Notification.publicVersion]
- privateVersion.toDefaultPublicVersion()
- }
+ notification.publicVersion?.let { publicNotification ->
+ createAppDefinedPublicVersion(
+ privateModel = privateVersion,
+ publicNotification = publicNotification,
+ imageModelProvider = imageModelProvider,
+ )
+ } ?: createDefaultPublicVersion(privateModel = privateVersion)
}
return PromotedNotificationContentModels(
privateVersion = privateVersion,
@@ -126,19 +127,59 @@ constructor(
.also { logger.logExtractionSucceeded(entry, it) }
}
- private fun PromotedNotificationContentModel.toDefaultPublicVersion():
- PromotedNotificationContentModel =
- PromotedNotificationContentModel.Builder(key = identity.key).let {
- it.style = if (style == Style.Ineligible) Style.Ineligible else Style.Base
- it.smallIcon = smallIcon
- it.iconLevel = iconLevel
- it.appName = appName
- it.time = time
- it.lastAudiblyAlertedMs = lastAudiblyAlertedMs
- it.profileBadgeResId = profileBadgeResId
- it.colors = colors
- it.build()
- }
+ private fun copyNonSensitiveFields(
+ privateModel: PromotedNotificationContentModel,
+ publicBuilder: PromotedNotificationContentModel.Builder,
+ ) {
+ publicBuilder.smallIcon = privateModel.smallIcon
+ publicBuilder.iconLevel = privateModel.iconLevel
+ publicBuilder.appName = privateModel.appName
+ publicBuilder.time = privateModel.time
+ publicBuilder.lastAudiblyAlertedMs = privateModel.lastAudiblyAlertedMs
+ publicBuilder.profileBadgeResId = privateModel.profileBadgeResId
+ publicBuilder.colors = privateModel.colors
+ }
+
+ private fun createDefaultPublicVersion(
+ privateModel: PromotedNotificationContentModel
+ ): PromotedNotificationContentModel =
+ PromotedNotificationContentModel.Builder(key = privateModel.identity.key)
+ .also {
+ it.style =
+ if (privateModel.style == Style.Ineligible) Style.Ineligible else Style.Base
+ copyNonSensitiveFields(privateModel, it)
+ }
+ .build()
+
+ private fun createAppDefinedPublicVersion(
+ privateModel: PromotedNotificationContentModel,
+ publicNotification: Notification,
+ imageModelProvider: ImageModelProvider,
+ ): PromotedNotificationContentModel =
+ PromotedNotificationContentModel.Builder(key = privateModel.identity.key)
+ .also { publicBuilder ->
+ val notificationStyle = publicNotification.notificationStyle
+ publicBuilder.style =
+ when {
+ privateModel.style == Style.Ineligible -> Style.Ineligible
+ notificationStyle == CallStyle::class.java -> Style.CollapsedCall
+ else -> Style.CollapsedBase
+ }
+ copyNonSensitiveFields(privateModel = privateModel, publicBuilder = publicBuilder)
+ publicBuilder.shortCriticalText = publicNotification.shortCriticalText()
+ publicBuilder.subText = publicNotification.subText()
+ // The standard public version is extracted as a collapsed notification,
+ // so avoid using bigTitle or bigText, and instead get the collapsed versions.
+ publicBuilder.title = publicNotification.title(notificationStyle, expanded = false)
+ publicBuilder.text = publicNotification.text()
+ publicBuilder.skeletonLargeIcon =
+ publicNotification.skeletonLargeIcon(imageModelProvider)
+ // Only CallStyle has styled content that shows in the collapsed version.
+ if (publicBuilder.style == Style.Call) {
+ extractCallStyleContent(publicNotification, publicBuilder, imageModelProvider)
+ }
+ }
+ .build()
private fun extractPrivateContent(
key: String,
@@ -163,8 +204,8 @@ constructor(
contentBuilder.shortCriticalText = notification.shortCriticalText()
contentBuilder.lastAudiblyAlertedMs = lastAudiblyAlertedMs
contentBuilder.profileBadgeResId = null // TODO
- contentBuilder.title = notification.title(recoveredBuilder.style)
- contentBuilder.text = notification.text(recoveredBuilder.style)
+ contentBuilder.title = notification.title(recoveredBuilder.style?.javaClass)
+ contentBuilder.text = notification.text(recoveredBuilder.style?.javaClass)
contentBuilder.skeletonLargeIcon = notification.skeletonLargeIcon(imageModelProvider)
contentBuilder.oldProgress = notification.oldProgress()
@@ -191,12 +232,16 @@ constructor(
private fun Notification.callPerson(): Person? =
extras?.getParcelable(EXTRA_CALL_PERSON, Person::class.java)
- private fun Notification.title(style: Notification.Style?): CharSequence? {
- return when (style) {
- is BigTextStyle,
- is BigPictureStyle,
- is InboxStyle -> bigTitle()
- is CallStyle -> callPerson()?.name
+ private fun Notification.title(
+ styleClass: Class<out Notification.Style>?,
+ expanded: Boolean = true,
+ ): CharSequence? {
+ // bigTitle is only used in the expanded form of 3 styles.
+ return when (styleClass) {
+ BigTextStyle::class.java,
+ BigPictureStyle::class.java,
+ InboxStyle::class.java -> if (expanded) bigTitle() else null
+ CallStyle::class.java -> callPerson()?.name?.takeUnlessEmpty()
else -> null
} ?: title()
}
@@ -206,9 +251,9 @@ constructor(
private fun Notification.bigText(): CharSequence? =
getCharSequenceExtraUnlessEmpty(EXTRA_BIG_TEXT)
- private fun Notification.text(style: Notification.Style?): CharSequence? {
- return when (style) {
- is BigTextStyle -> bigText()
+ private fun Notification.text(styleClass: Class<out Notification.Style>?): CharSequence? {
+ return when (styleClass) {
+ BigTextStyle::class.java -> bigText()
else -> null
} ?: text()
}
@@ -293,17 +338,15 @@ constructor(
null -> Style.Base
is BigPictureStyle -> {
- style.extractContent(contentBuilder)
Style.BigPicture
}
is BigTextStyle -> {
- style.extractContent(contentBuilder)
Style.BigText
}
is CallStyle -> {
- style.extractContent(notification, contentBuilder, imageModelProvider)
+ extractCallStyleContent(notification, contentBuilder, imageModelProvider)
Style.Call
}
@@ -316,19 +359,7 @@ constructor(
}
}
- private fun BigPictureStyle.extractContent(
- contentBuilder: PromotedNotificationContentModel.Builder
- ) {
- // Big title is handled in resolveTitle, and big picture is unsupported.
- }
-
- private fun BigTextStyle.extractContent(
- contentBuilder: PromotedNotificationContentModel.Builder
- ) {
- // Big title and big text are handled in resolveTitle and resolveText.
- }
-
- private fun CallStyle.extractContent(
+ private fun extractCallStyleContent(
notification: Notification,
contentBuilder: PromotedNotificationContentModel.Builder,
imageModelProvider: ImageModelProvider,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt
deleted file mode 100644
index 5c0991059dec..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.promoted
-
-import android.app.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-// NOTE: We're merging this flag with the `ui_rich_ongoing` flag.
-// We'll replace all usages of this class with PromotedNotificationUi as a follow-up.
-
-/** Helper for reading or using the expanded ui rich ongoing flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object PromotedNotificationUiForceExpanded {
- /** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_UI_RICH_ONGOING
-
- /** A token used for dependency declaration */
- val token: FlagToken
- get() = FlagToken(FLAG_NAME, isEnabled)
-
- /** Is the refactor enabled */
- @JvmStatic
- inline val isEnabled
- get() = Flags.uiRichOngoing()
-
- /**
- * Called to ensure code is only run when the flag is enabled. This protects users from the
- * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
- * build to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun isUnexpectedlyInLegacyMode() =
- RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
-
- /**
- * Called to ensure code is only run when the flag is enabled. This will throw an exception if
- * the flag is not enabled to ensure that the refactor author catches issues in testing.
- * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
- */
- @JvmStatic
- @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
- inline fun unsafeAssertInNewMode() =
- RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
-
- /**
- * Called to ensure code is only run when the flag is disabled. This will throw an exception if
- * the flag is enabled to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
index 339a5bb29a34..ae6b2cc6cb1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/shared/model/PromotedNotificationContentModel.kt
@@ -174,9 +174,11 @@ data class PromotedNotificationContentModel(
/** The promotion-eligible style of a notification, or [Style.Ineligible] if not. */
enum class Style {
Base, // style == null
+ CollapsedBase, // style == null
BigPicture,
BigText,
Call,
+ CollapsedCall,
Progress,
Ineligible,
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 740391d7010e..3fed78674cf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -128,7 +128,6 @@ import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
import com.android.systemui.statusbar.notification.row.shared.AsyncGroupHeaderViewInflation;
import com.android.systemui.statusbar.notification.row.shared.LockscreenOtpRedaction;
import com.android.systemui.statusbar.notification.row.ui.viewmodel.BundleHeaderViewModelImpl;
@@ -882,7 +881,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
private void updateLimitsForView(NotificationContentView layout) {
final int maxExpandedHeight;
- if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) {
+ if (isPromotedOngoing()) {
maxExpandedHeight = mMaxExpandedHeightForPromotedOngoing;
} else {
maxExpandedHeight = mMaxExpandedHeight;
@@ -1381,7 +1380,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mIsSummaryWithChildren) {
return mChildrenContainer.getIntrinsicHeight();
}
- if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) {
+ if (isPromotedOngoing()) {
return getMaxExpandHeight();
}
if (mExpandedWhenPinned) {
@@ -3030,7 +3029,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
if (mIsSummaryWithChildren && !shouldShowPublic()) {
return !mChildrenExpanded;
}
- if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) {
+ if (isPromotedOngoing()) {
return false;
}
return mEnableNonGroupedNotificationExpand && mExpandable;
@@ -3141,7 +3140,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public void setUserLocked(boolean userLocked) {
- if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) return;
+ if (isPromotedOngoing()) return;
mUserLocked = userLocked;
mPrivateLayout.setUserExpanding(userLocked);
@@ -3411,7 +3410,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public boolean isExpanded(boolean allowOnKeyguard) {
- if (PromotedNotificationUiForceExpanded.isEnabled() && isPromotedOngoing()) {
+ if (isPromotedOngoing()) {
return isPromotedNotificationExpanded(allowOnKeyguard);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowImageInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowImageInflater.kt
index 7bac17f4c227..215988471f00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowImageInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowImageInflater.kt
@@ -21,7 +21,7 @@ import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import androidx.annotation.VisibleForTesting
import com.android.app.tracing.traceSection
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
import com.android.systemui.statusbar.notification.row.shared.IconData
import com.android.systemui.statusbar.notification.row.shared.ImageModel
import com.android.systemui.statusbar.notification.row.shared.ImageModelProvider
@@ -80,7 +80,7 @@ interface RowImageInflater {
companion object {
@Suppress("NOTHING_TO_INLINE")
@JvmStatic
- inline fun featureFlagEnabled() = PromotedNotificationUiAod.isEnabled
+ inline fun featureFlagEnabled() = PromotedNotificationUi.isEnabled
@JvmStatic
fun newInstance(previousIndex: ImageModelIndex?, reinflating: Boolean): RowImageInflater =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
index d5e2e7eb3a9c..c4fe25031de3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java
@@ -50,7 +50,6 @@ import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.notification.ImageTransformState;
import com.android.systemui.statusbar.notification.TransformState;
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.HybridNotificationView;
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
@@ -196,8 +195,7 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
}
private void adjustTitleAndRightIconForPromotedOngoing() {
- if (PromotedNotificationUiForceExpanded.isEnabled() &&
- mRow.isPromotedOngoing() && mRightIcon != null) {
+ if (mRow.isPromotedOngoing() && mRightIcon != null) {
final int horizontalMargin;
if (notificationsRedesignTemplates()) {
horizontalMargin = mView.getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/AvalancheReplaceHunWhenCritical.kt
index 69e27dcc2e6c..0ccd6064d9a3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/AvalancheReplaceHunWhenCritical.kt
@@ -14,19 +14,17 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.promoted
+package com.android.systemui.statusbar.notification.shared
-import android.app.Flags
+import com.android.systemui.Flags
import com.android.systemui.flags.FlagToken
import com.android.systemui.flags.RefactorFlagUtils
-// NOTE: We're merging this flag with the `ui_rich_ongoing` flag.
-// We'll replace all usages of this class with PromotedNotificationUi as a follow-up.
-
-/** Helper for reading or using the promoted ongoing notifications AOD flag state. */
-object PromotedNotificationUiAod {
+/** Helper for reading or using the avalanche replace Hun when critical flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object AvalancheReplaceHunWhenCritical {
/** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_UI_RICH_ONGOING
+ const val FLAG_NAME = Flags.FLAG_AVALANCHE_REPLACE_HUN_WHEN_CRITICAL
/** A token used for dependency declaration */
val token: FlagToken
@@ -35,7 +33,7 @@ object PromotedNotificationUiAod {
/** Is the refactor enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.uiRichOngoing()
+ get() = Flags.avalancheReplaceHunWhenCritical()
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
@@ -48,16 +46,6 @@ object PromotedNotificationUiAod {
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
- * the flag is not enabled to ensure that the refactor author catches issues in testing.
- * Caution!! Using this check incorrectly will cause crashes in nextfood builds!
- */
- @JvmStatic
- @Deprecated("Avoid crashing.", ReplaceWith("if (this.isUnexpectedlyInLegacyMode()) return"))
- inline fun unsafeAssertInNewMode() =
- RefactorFlagUtils.unsafeAssertInNewMode(isEnabled, FLAG_NAME)
-
- /**
- * Called to ensure code is only run when the flag is disabled. This will throw an exception if
* the flag is enabled to ensure that the refactor author catches issues in testing.
*/
@JvmStatic
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index e5071d9c1e53..58df1703a925 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -29,7 +29,6 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.shared.NotificationBundleUi
@@ -476,9 +475,7 @@ constructor(
if (onLockscreen) {
if (
view is ExpandableNotificationRow &&
- (canPeek ||
- (PromotedNotificationUiForceExpanded.isEnabled &&
- view.isPromotedOngoing))
+ (canPeek || view.isPromotedOngoing)
) {
height
} else {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
index eb72acc0dade..ca8fae875244 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaSwitchingControllerTest.java
@@ -1483,6 +1483,44 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
verify(mLocalMediaManager, atLeastOnce()).connectDevice(outputMediaDevice);
}
+ @Test
+ public void connectDeviceButton_remoteDevice_noButton() {
+ when(mMediaDevice1.getFeatures()).thenReturn(
+ ImmutableList.of(MediaRoute2Info.FEATURE_REMOTE_PLAYBACK));
+ when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
+ mMediaSwitchingController.start(mCb);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+
+ List<MediaItem> resultList = mMediaSwitchingController.getMediaItemList();
+
+ assertThat(getNumberOfConnectDeviceButtons(resultList)).isEqualTo(0);
+ }
+
+ @Test
+ public void connectDeviceButton_localDevice_hasButton() {
+ when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
+ mMediaSwitchingController.start(mCb);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+
+ List<MediaItem> resultList = mMediaSwitchingController.getMediaItemList();
+
+ assertThat(getNumberOfConnectDeviceButtons(resultList)).isEqualTo(1);
+ assertThat(resultList.get(resultList.size() - 1).getMediaItemType()).isEqualTo(
+ MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE);
+ }
+
+ @Test
+ public void connectDeviceButton_localDeviceButtonDisabledByParam_noButton() {
+ when(mLocalMediaManager.getCurrentConnectedDevice()).thenReturn(mMediaDevice1);
+ mMediaSwitchingController.start(mCb);
+ mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
+
+ List<MediaItem> resultList = mMediaSwitchingController.getMediaItemList(
+ false /* addConnectDeviceButton */);
+
+ assertThat(getNumberOfConnectDeviceButtons(resultList)).isEqualTo(0);
+ }
+
@DisableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
@Test
public void connectDeviceButton_presentAtAllTimesForNonGroupOutputs() {
@@ -1495,7 +1533,8 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
.getSelectedMediaDevice();
// Verify that there is initially one "Connect a device" button present.
- assertThat(getNumberOfConnectDeviceButtons()).isEqualTo(1);
+ assertThat(getNumberOfConnectDeviceButtons(
+ mMediaSwitchingController.getMediaItemList())).isEqualTo(1);
// Change the selected device, and verify that there is still one "Connect a device" button
// present.
@@ -1504,7 +1543,8 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
.getSelectedMediaDevice();
mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
- assertThat(getNumberOfConnectDeviceButtons()).isEqualTo(1);
+ assertThat(getNumberOfConnectDeviceButtons(
+ mMediaSwitchingController.getMediaItemList())).isEqualTo(1);
}
@EnableFlags(Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL)
@@ -1523,7 +1563,8 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
doReturn(selectedInputMediaDevice).when(mInputRouteManager).getSelectedInputDevice();
// Verify that there is initially one "Connect a device" button present.
- assertThat(getNumberOfConnectDeviceButtons()).isEqualTo(1);
+ assertThat(getNumberOfConnectDeviceButtons(
+ mMediaSwitchingController.getMediaItemList())).isEqualTo(1);
// Change the selected device, and verify that there is still one "Connect a device" button
// present.
@@ -1532,7 +1573,8 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
.getSelectedMediaDevice();
mMediaSwitchingController.onDeviceListUpdate(mMediaDevices);
- assertThat(getNumberOfConnectDeviceButtons()).isEqualTo(1);
+ assertThat(getNumberOfConnectDeviceButtons(
+ mMediaSwitchingController.getMediaItemList())).isEqualTo(1);
}
@EnableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING)
@@ -1633,7 +1675,7 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
when(mLocalMediaManager.isPreferenceRouteListingExist()).thenReturn(false);
mMediaSwitchingController.start(mCb);
reset(mCb);
- mMediaSwitchingController.getMediaItemList().clear();
+ mMediaSwitchingController.clearMediaItemList();
}
@DisableFlags(Flags.FLAG_ENABLE_OUTPUT_SWITCHER_DEVICE_GROUPING)
@@ -1691,9 +1733,9 @@ public class MediaSwitchingControllerTest extends SysuiTestCase {
assertThat(items.get(0).isFirstDeviceInGroup()).isTrue();
}
- private int getNumberOfConnectDeviceButtons() {
+ private int getNumberOfConnectDeviceButtons(List<MediaItem> itemList) {
int numberOfConnectDeviceButtons = 0;
- for (MediaItem item : mMediaSwitchingController.getMediaItemList()) {
+ for (MediaItem item : itemList) {
if (item.getMediaItemType() == MediaItem.MediaItemType.TYPE_PAIR_NEW_DEVICE) {
numberOfConnectDeviceButtons++;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/OutputMediaItemListProxyTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/OutputMediaItemListProxyTest.java
index f6edd49f142f..11a3670c20f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/OutputMediaItemListProxyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/OutputMediaItemListProxyTest.java
@@ -58,7 +58,6 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase {
private MediaItem mMediaItem1;
private MediaItem mMediaItem2;
- private MediaItem mConnectNewDeviceMediaItem;
private OutputMediaItemListProxy mOutputMediaItemListProxy;
@Parameters(name = "{0}")
@@ -83,7 +82,6 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase {
when(mMediaDevice4.getId()).thenReturn(DEVICE_ID_4);
mMediaItem1 = MediaItem.createDeviceMediaItem(mMediaDevice1);
mMediaItem2 = MediaItem.createDeviceMediaItem(mMediaDevice2);
- mConnectNewDeviceMediaItem = MediaItem.createPairNewDeviceMediaItem();
mOutputMediaItemListProxy = new OutputMediaItemListProxy(mContext);
}
@@ -98,8 +96,7 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase {
/* devices= */ List.of(mMediaDevice2, mMediaDevice3),
/* selectedDevices */ List.of(mMediaDevice3),
/* connectedMediaDevice= */ null,
- /* needToHandleMutingExpectedDevice= */ false,
- /* connectNewDeviceMediaItem= */ null);
+ /* needToHandleMutingExpectedDevice= */ false);
// Check the output media items to be
// * a media item with the selected mMediaDevice3
@@ -115,8 +112,7 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase {
/* devices= */ List.of(mMediaDevice4, mMediaDevice1, mMediaDevice3, mMediaDevice2),
/* selectedDevices */ List.of(mMediaDevice3),
/* connectedMediaDevice= */ null,
- /* needToHandleMutingExpectedDevice= */ false,
- /* connectNewDeviceMediaItem= */ null);
+ /* needToHandleMutingExpectedDevice= */ false);
// Check the output media items to be
// * a media item with the selected route mMediaDevice3
@@ -136,8 +132,7 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase {
/* devices= */ List.of(mMediaDevice1, mMediaDevice3, mMediaDevice2),
/* selectedDevices */ List.of(mMediaDevice1, mMediaDevice3),
/* connectedMediaDevice= */ null,
- /* needToHandleMutingExpectedDevice= */ false,
- /* connectNewDeviceMediaItem= */ null);
+ /* needToHandleMutingExpectedDevice= */ false);
// Check the output media items to be
// * a media item with the selected route mMediaDevice3
@@ -161,8 +156,7 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase {
/* devices= */ List.of(mMediaDevice2, mMediaDevice4, mMediaDevice3, mMediaDevice1),
/* selectedDevices */ List.of(mMediaDevice1, mMediaDevice2, mMediaDevice3),
/* connectedMediaDevice= */ null,
- /* needToHandleMutingExpectedDevice= */ false,
- /* connectNewDeviceMediaItem= */ null);
+ /* needToHandleMutingExpectedDevice= */ false);
if (Flags.enableOutputSwitcherDeviceGrouping()) {
// When the device grouping is enabled, the order of selected devices are preserved:
@@ -197,8 +191,7 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase {
/* devices= */ List.of(mMediaDevice4, mMediaDevice1, mMediaDevice3, mMediaDevice2),
/* selectedDevices */ List.of(mMediaDevice2, mMediaDevice3),
/* connectedMediaDevice= */ null,
- /* needToHandleMutingExpectedDevice= */ false,
- /* connectNewDeviceMediaItem= */ null);
+ /* needToHandleMutingExpectedDevice= */ false);
if (Flags.enableOutputSwitcherDeviceGrouping()) {
// When the device grouping is enabled, the order of selected devices are preserved:
@@ -233,8 +226,7 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase {
/* devices= */ List.of(mMediaDevice1, mMediaDevice3, mMediaDevice4),
/* selectedDevices */ List.of(mMediaDevice3),
/* connectedMediaDevice= */ null,
- /* needToHandleMutingExpectedDevice= */ false,
- /* connectNewDeviceMediaItem= */ null);
+ /* needToHandleMutingExpectedDevice= */ false);
if (Flags.enableOutputSwitcherDeviceGrouping()) {
// When the device grouping is enabled, the order of selected devices are preserved:
@@ -261,47 +253,6 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase {
}
}
- @EnableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION)
- @Test
- public void updateMediaDevices_withConnectNewDeviceMediaItem_shouldUpdateMediaItemList() {
- assertThat(mOutputMediaItemListProxy.isEmpty()).isTrue();
-
- // Create the initial output media item list with a connect new device media item.
- mOutputMediaItemListProxy.updateMediaDevices(
- /* devices= */ List.of(mMediaDevice2, mMediaDevice3),
- /* selectedDevices */ List.of(mMediaDevice3),
- /* connectedMediaDevice= */ null,
- /* needToHandleMutingExpectedDevice= */ false,
- mConnectNewDeviceMediaItem);
-
- // Check the output media items to be
- // * a media item with the selected mMediaDevice3
- // * a group divider for suggested devices
- // * a media item with the mMediaDevice2
- // * a connect new device media item
- assertThat(mOutputMediaItemListProxy.getOutputMediaItemList())
- .contains(mConnectNewDeviceMediaItem);
- assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList()))
- .containsExactly(mMediaDevice3, null, mMediaDevice2, null);
-
- // Update the output media item list without a connect new device media item.
- mOutputMediaItemListProxy.updateMediaDevices(
- /* devices= */ List.of(mMediaDevice2, mMediaDevice3),
- /* selectedDevices */ List.of(mMediaDevice3),
- /* connectedMediaDevice= */ null,
- /* needToHandleMutingExpectedDevice= */ false,
- /* connectNewDeviceMediaItem= */ null);
-
- // Check the output media items to be
- // * a media item with the selected mMediaDevice3
- // * a group divider for suggested devices
- // * a media item with the mMediaDevice2
- assertThat(mOutputMediaItemListProxy.getOutputMediaItemList())
- .doesNotContain(mConnectNewDeviceMediaItem);
- assertThat(getMediaDevices(mOutputMediaItemListProxy.getOutputMediaItemList()))
- .containsExactly(mMediaDevice3, null, mMediaDevice2);
- }
-
@DisableFlags(Flags.FLAG_FIX_OUTPUT_MEDIA_ITEM_LIST_INDEX_OUT_OF_BOUNDS_EXCEPTION)
@Test
public void clearAndAddAll_shouldUpdateMediaItemList() {
@@ -325,8 +276,7 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase {
/* devices= */ List.of(mMediaDevice1),
/* selectedDevices */ List.of(),
/* connectedMediaDevice= */ null,
- /* needToHandleMutingExpectedDevice= */ false,
- /* connectNewDeviceMediaItem= */ null);
+ /* needToHandleMutingExpectedDevice= */ false);
assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse();
mOutputMediaItemListProxy.clear();
@@ -354,8 +304,7 @@ public class OutputMediaItemListProxyTest extends SysuiTestCase {
/* devices= */ List.of(mMediaDevice1),
/* selectedDevices */ List.of(),
/* connectedMediaDevice= */ null,
- /* needToHandleMutingExpectedDevice= */ false,
- /* connectNewDeviceMediaItem= */ null);
+ /* needToHandleMutingExpectedDevice= */ false);
assertThat(mOutputMediaItemListProxy.isEmpty()).isFalse();
mOutputMediaItemListProxy.removeMutingExpectedDevices();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index 7728f684f0f2..c21570928bde 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -49,6 +49,7 @@ import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.ui.compose.CommunalContent
import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
import com.android.systemui.communal.util.CommunalColors
+import com.android.systemui.communal.util.userTouchActivityNotifier
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
@@ -64,6 +65,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.controller.keyguardMediaController
+import com.android.systemui.power.data.repository.fakePowerRepository
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -137,6 +139,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
notificationStackScrollLayoutController,
keyguardMediaController,
lockscreenSmartspaceController,
+ userTouchActivityNotifier,
logcatLogBuffer("GlanceableHubContainerControllerTest"),
kosmos.userActivityNotifier,
)
@@ -178,6 +181,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
notificationStackScrollLayoutController,
keyguardMediaController,
lockscreenSmartspaceController,
+ userTouchActivityNotifier,
logcatLogBuffer("GlanceableHubContainerControllerTest"),
kosmos.userActivityNotifier,
)
@@ -208,6 +212,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
notificationStackScrollLayoutController,
keyguardMediaController,
lockscreenSmartspaceController,
+ userTouchActivityNotifier,
logcatLogBuffer("GlanceableHubContainerControllerTest"),
kosmos.userActivityNotifier,
)
@@ -234,6 +239,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
notificationStackScrollLayoutController,
keyguardMediaController,
lockscreenSmartspaceController,
+ userTouchActivityNotifier,
logcatLogBuffer("GlanceableHubContainerControllerTest"),
kosmos.userActivityNotifier,
)
@@ -539,6 +545,18 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
@Test
+ fun onTouchEvent_touchHandled_notifyUserActivity() =
+ kosmos.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ // Touch event is sent to the container view.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+ verify(containerView).onTouchEvent(DOWN_EVENT)
+ assertThat(fakePowerRepository.userTouchRegistered).isTrue()
+ }
+
+ @Test
fun onTouchEvent_editActivityShowing_touchesConsumedButNotDispatched() =
kosmos.runTest {
// Communal is open.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index eae23e70027b..e70ce53e74cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -83,7 +83,6 @@ import com.android.systemui.statusbar.notification.headsup.HeadsUpManager;
import com.android.systemui.statusbar.notification.headsup.PinnedStatus;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi;
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiForceExpanded;
import com.android.systemui.statusbar.notification.row.ExpandableView.OnHeightChangedListener;
import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProvider;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
@@ -948,7 +947,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@Test
- @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME})
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
@DisableFlags(NotificationBundleUi.FLAG_NAME)
public void isExpanded_sensitivePromotedNotification_notExpanded() throws Exception {
// GIVEN
@@ -965,7 +964,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@Test
- @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME})
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
@DisableFlags(NotificationBundleUi.FLAG_NAME)
public void isExpanded_promotedNotificationNotOnKeyguard_expanded() throws Exception {
// GIVEN
@@ -981,7 +980,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@Test
- @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME})
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
@DisableFlags(NotificationBundleUi.FLAG_NAME)
public void isExpanded_promotedNotificationAllowOnKeyguard_expanded() throws Exception {
// GIVEN
@@ -997,7 +996,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@Test
- @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME})
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
@DisableFlags(NotificationBundleUi.FLAG_NAME)
public void isExpanded_promotedNotificationIgnoreLockscreenConstraints_expanded()
throws Exception {
@@ -1035,7 +1034,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@Test
- @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME})
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
@DisableFlags(NotificationBundleUi.FLAG_NAME)
public void isExpanded_promotedNotificationSaveSpaceOnLockScreen_notExpanded()
throws Exception {
@@ -1053,7 +1052,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@Test
- @EnableFlags({PromotedNotificationUi.FLAG_NAME, PromotedNotificationUiForceExpanded.FLAG_NAME})
+ @EnableFlags(PromotedNotificationUi.FLAG_NAME)
@DisableFlags(NotificationBundleUi.FLAG_NAME)
public void isExpanded_promotedNotificationNotSaveSpaceOnLockScreen_expanded()
throws Exception {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt
index d0357603665d..4d9f20c0038f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowImageInflaterTest.kt
@@ -20,7 +20,7 @@ import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUiAod
+import com.android.systemui.statusbar.notification.promoted.PromotedNotificationUi
import com.android.systemui.statusbar.notification.row.shared.IconData
import com.android.systemui.statusbar.notification.row.shared.ImageModel
import com.android.systemui.statusbar.notification.row.shared.ImageModelProvider.ImageSizeClass.SmallSquare
@@ -31,7 +31,7 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
-@EnableFlags(PromotedNotificationUiAod.FLAG_NAME)
+@EnableFlags(PromotedNotificationUi.FLAG_NAME)
class RowImageInflaterTest : SysuiTestCase() {
private lateinit var rowImageInflater: RowImageInflater
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/UserTouchActivityNotifierKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/UserTouchActivityNotifierKosmos.kt
new file mode 100644
index 000000000000..3452d097d3da
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/UserTouchActivityNotifierKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.util
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.backgroundScope
+import com.android.systemui.power.domain.interactor.powerInteractor
+
+val Kosmos.userTouchActivityNotifier by
+ Kosmos.Fixture {
+ UserTouchActivityNotifier(backgroundScope, powerInteractor, USER_TOUCH_ACTIVITY_RATE_LIMIT)
+ }
+
+const val USER_TOUCH_ACTIVITY_RATE_LIMIT = 100
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
index 23251d27cff9..90e23290e9e9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/NotificationsShadeOverlayContentViewModelKosmos.kt
@@ -22,9 +22,7 @@ import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarou
import com.android.systemui.notifications.ui.viewmodel.NotificationsShadeOverlayContentViewModel
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.domain.interactor.shadeInteractor
-import com.android.systemui.shade.domain.interactor.shadeModeInteractor
import com.android.systemui.statusbar.disableflags.domain.interactor.disableFlagsInteractor
-import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModelFactory
val Kosmos.notificationsShadeOverlayContentViewModel:
@@ -34,9 +32,7 @@ val Kosmos.notificationsShadeOverlayContentViewModel:
notificationsPlaceholderViewModelFactory = notificationsPlaceholderViewModelFactory,
sceneInteractor = sceneInteractor,
shadeInteractor = shadeInteractor,
- shadeModeInteractor = shadeModeInteractor,
disableFlagsInteractor = disableFlagsInteractor,
mediaCarouselInteractor = mediaCarouselInteractor,
- activeNotificationsInteractor = activeNotificationsInteractor,
)
}
diff --git a/services/art-profile-extra b/services/art-profile-extra
index 54362411e5ea..9cbc03903904 100644
--- a/services/art-profile-extra
+++ b/services/art-profile-extra
@@ -1 +1,9 @@
HSPLcom/android/server/am/ActivityManagerService$LocalService;->checkContentProviderAccess(Ljava/lang/String;I)Ljava/lang/String;
+HSPLcom/android/server/am/ActivityManagerService$LocalService;->updateDeviceIdleTempAllowlist([IIZJIILjava/lang/String;I)V
+HSPLcom/android/server/am/OomAdjuster;->setUidTempAllowlistStateLSP(IZ)V
+HSPLcom/android/server/am/BatteryStatsService;->setBatteryState(IIIIIIIIJ)V
+HSPLcom/android/server/pm/PackageManagerService$IPackageManagerImpl;->setComponentEnabledSetting(Landroid/content/ComponentName;IIILjava/lang/String;)V
+HSPLcom/android/server/am/ActiveServices;->bindServiceLocked(Landroid/app/IApplicationThread;Landroid/os/IBinder;Landroid/content/Intent;Ljava/lang/String;Landroid/app/IServiceConnection;JLjava/lang/String;ZILjava/lang/String;Landroid/app/IApplicationThread;Ljava/lang/String;I)I
+HSPLcom/android/server/accessibility/AccessibilityManagerService;->onServiceInfoChangedLocked(Lcom/android/server/accessibility/AccessibilityUserState;)V
+HSPLcom/android/server/clipboard/ClipboardService$ClipboardImpl;->checkAndSetPrimaryClip(Landroid/content/ClipData;Ljava/lang/String;Ljava/lang/String;IILjava/lang/String;)V
+HSPLcom/android/server/clipboard/ClipboardService$ClipboardImpl;->getPrimaryClip(Ljava/lang/String;Ljava/lang/String;II)Landroid/content/ClipData;
diff --git a/services/core/java/com/android/server/GestureLauncherService.java b/services/core/java/com/android/server/GestureLauncherService.java
index 87222a60d82d..28258ae47a65 100644
--- a/services/core/java/com/android/server/GestureLauncherService.java
+++ b/services/core/java/com/android/server/GestureLauncherService.java
@@ -19,7 +19,6 @@ package com.android.server;
import static android.service.quickaccesswallet.Flags.launchWalletOptionOnPowerDoubleTap;
import static android.service.quickaccesswallet.Flags.launchWalletViaSysuiCallbacks;
-import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
import static com.android.internal.R.integer.config_defaultMinEmergencyGestureTapDurationMillis;
import android.app.ActivityManager;
@@ -635,46 +634,6 @@ public class GestureLauncherService extends SystemService {
}
/**
- * Processes a power key event in GestureLauncherService without performing an action. This
- * method is called on every KEYCODE_POWER ACTION_DOWN event and ensures that, even if
- * KEYCODE_POWER events are passed to and handled by the app, the GestureLauncherService still
- * keeps track of all running KEYCODE_POWER events for its gesture detection and relevant
- * actions.
- */
- public void processPowerKeyDown(KeyEvent event) {
- if (mEmergencyGestureEnabled && mEmergencyGesturePowerButtonCooldownPeriodMs >= 0
- && event.getEventTime() - mLastEmergencyGestureTriggered
- < mEmergencyGesturePowerButtonCooldownPeriodMs) {
- return;
- }
- if (event.isLongPress()) {
- return;
- }
-
- final long powerTapInterval;
-
- synchronized (this) {
- powerTapInterval = event.getEventTime() - mLastPowerDown;
- mLastPowerDown = event.getEventTime();
- if (powerTapInterval >= POWER_SHORT_TAP_SEQUENCE_MAX_INTERVAL_MS) {
- // Tap too slow, reset consecutive tap counts.
- mFirstPowerDown = event.getEventTime();
- mPowerButtonConsecutiveTaps = 1;
- mPowerButtonSlowConsecutiveTaps = 1;
- } else if (powerTapInterval >= POWER_DOUBLE_TAP_MAX_TIME_MS) {
- // Tap too slow for shortcuts
- mFirstPowerDown = event.getEventTime();
- mPowerButtonConsecutiveTaps = 1;
- mPowerButtonSlowConsecutiveTaps++;
- } else if (!overridePowerKeyBehaviorInFocusedWindow() || powerTapInterval > 0) {
- // Fast consecutive tap
- mPowerButtonConsecutiveTaps++;
- mPowerButtonSlowConsecutiveTaps++;
- }
- }
- }
-
- /**
* Attempts to intercept power key down event by detecting certain gesture patterns
*
* @param interactive true if the event's policy contains {@code FLAG_INTERACTIVE}
@@ -721,7 +680,7 @@ public class GestureLauncherService extends SystemService {
mFirstPowerDown = event.getEventTime();
mPowerButtonConsecutiveTaps = 1;
mPowerButtonSlowConsecutiveTaps++;
- } else if (powerTapInterval > 0) {
+ } else {
// Fast consecutive tap
mPowerButtonConsecutiveTaps++;
mPowerButtonSlowConsecutiveTaps++;
diff --git a/services/core/java/com/android/server/audio/HardeningEnforcer.java b/services/core/java/com/android/server/audio/HardeningEnforcer.java
index 9bb5160f108a..46693614e137 100644
--- a/services/core/java/com/android/server/audio/HardeningEnforcer.java
+++ b/services/core/java/com/android/server/audio/HardeningEnforcer.java
@@ -199,7 +199,9 @@ public class HardeningEnforcer {
if (packageName.isEmpty()) {
packageName = getPackNameForUid(callingUid);
}
-
+ // indicates would be blocked if audio capabilities were required
+ boolean blockedIfFull = !noteOp(AppOpsManager.OP_CONTROL_AUDIO,
+ callingUid, packageName, attributionTag);
boolean blocked = true;
// indicates the focus request was not blocked because of the SDK version
boolean unblockedBySdk = false;
@@ -213,22 +215,35 @@ public class HardeningEnforcer {
Slog.i(TAG, "blockFocusMethod pack:" + packageName + " NOT blocking due to sdk="
+ targetSdk);
}
- blocked = false;
unblockedBySdk = true;
}
- metricsLogFocusReq(blocked, focusReqType, callingUid, unblockedBySdk);
+ boolean enforced = mShouldEnableAllHardening.get() || !unblockedBySdk;
+ boolean enforcedFull = mShouldEnableAllHardening.get();
- if (!blocked) {
- return false;
- }
+ metricsLogFocusReq(blocked && enforced, focusReqType, callingUid, unblockedBySdk);
- String errorMssg = "Focus request DENIED for uid:" + callingUid
- + " clientId:" + clientId + " req:" + focusReqType
- + " procState:" + mActivityManager.getUidProcessState(callingUid);
- mEventLogger.enqueueAndSlog(errorMssg, EventLogger.Event.ALOGI, TAG);
+ if (blocked) {
+ String msg = "AudioHardening focus request for req "
+ + focusReqType
+ + (!enforced ? " would be " : " ")
+ + "ignored for "
+ + packageName + " (" + callingUid + "), "
+ + clientId
+ + ", level: partial";
+ mEventLogger.enqueueAndSlog(msg, EventLogger.Event.ALOGW, TAG);
+ } else if (blockedIfFull) {
+ String msg = "AudioHardening focus request for req "
+ + focusReqType
+ + (!enforcedFull ? " would be " : " ")
+ + "ignored for "
+ + packageName + " (" + callingUid + "), "
+ + clientId
+ + ", level: full";
+ mEventLogger.enqueueAndSlog(msg, EventLogger.Event.ALOGW, TAG);
+ }
- return true;
+ return blocked && enforced || (blockedIfFull && enforcedFull);
}
/**
diff --git a/services/core/java/com/android/server/connectivity/PacProxyService.java b/services/core/java/com/android/server/connectivity/PacProxyService.java
index 2e90a3d86161..c8c1eddd53e7 100644
--- a/services/core/java/com/android/server/connectivity/PacProxyService.java
+++ b/services/core/java/com/android/server/connectivity/PacProxyService.java
@@ -36,6 +36,7 @@ import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.IServiceManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -355,7 +356,9 @@ public class PacProxyService extends IPacProxyManager.Stub {
} catch (RemoteException e1) {
Log.e(TAG, "Remote Exception", e1);
}
- ServiceManager.addService(PAC_SERVICE_NAME, binder);
+ // Do not cache the service, otherwise it will crash com.android.pacprocessor
+ ServiceManager.addService(PAC_SERVICE_NAME, binder, /* allowIsolated */ false,
+ IServiceManager.FLAG_IS_LAZY_SERVICE);
mProxyService = IProxyService.Stub.asInterface(binder);
if (mProxyService == null) {
Log.e(TAG, "No proxy service");
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index f2ededaa2a9c..07530e1c6f7b 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -344,6 +344,9 @@ public class InputManagerService extends IInputManager.Stub
// Manages battery state for input devices.
private final BatteryController mBatteryController;
+ // Monitors any changes to the sysfs nodes when an input device is connected.
+ private final SysfsNodeMonitor mSysfsNodeMonitor;
+
@Nullable
private final TouchpadDebugViewController mTouchpadDebugViewController;
@@ -536,6 +539,8 @@ public class InputManagerService extends IInputManager.Stub
injector.getLooper(), this) : null;
mBatteryController = new BatteryController(mContext, mNative, injector.getLooper(),
injector.getUEventManager());
+ mSysfsNodeMonitor = new SysfsNodeMonitor(mContext, mNative, injector.getLooper(),
+ injector.getUEventManager());
mKeyboardBacklightController = injector.getKeyboardBacklightController(mNative);
mStickyModifierStateController = new StickyModifierStateController();
mInputDataStore = new InputDataStore();
@@ -665,6 +670,7 @@ public class InputManagerService extends IInputManager.Stub
mKeyboardLayoutManager.systemRunning();
mBatteryController.systemRunning();
+ mSysfsNodeMonitor.systemRunning();
mKeyboardBacklightController.systemRunning();
mKeyboardLedController.systemRunning();
mKeyRemapper.systemRunning();
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index ccf1a2c90876..de54cd81aa43 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -272,6 +272,9 @@ interface NativeInputManagerService {
/** Set whether showing a pointer icon for styluses is enabled. */
void setStylusPointerIconEnabled(boolean enabled);
+ /** Get the sysfs root path of an input device if known, otherwise return null. */
+ @Nullable String getSysfsRootPath(int deviceId);
+
/**
* Report sysfs node changes. This may result in recreation of the corresponding InputDevice.
* The recreated device may contain new associated peripheral devices like Light, Battery, etc.
@@ -619,6 +622,9 @@ interface NativeInputManagerService {
public native void setStylusPointerIconEnabled(boolean enabled);
@Override
+ public native String getSysfsRootPath(int deviceId);
+
+ @Override
public native void sysfsNodeChanged(String sysfsNodePath);
@Override
diff --git a/services/core/java/com/android/server/input/SysfsNodeMonitor.java b/services/core/java/com/android/server/input/SysfsNodeMonitor.java
new file mode 100644
index 000000000000..e55e1284d03c
--- /dev/null
+++ b/services/core/java/com/android/server/input/SysfsNodeMonitor.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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;
+
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UEventObserver;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import java.util.Objects;
+
+/**
+ * A thread-safe component of {@link InputManagerService} responsible for monitoring the addition
+ * of kernel sysfs nodes for newly connected input devices.
+ *
+ * This class uses the {@link UEventObserver} to monitor for changes to an input device's sysfs
+ * nodes, and is responsible for requesting the native code to refresh its sysfs nodes when there
+ * is a change. This is necessary because the sysfs nodes may only be configured after an input
+ * device is already added, with no way for the native code to detect any changes afterwards.
+ */
+final class SysfsNodeMonitor {
+ private static final String TAG = SysfsNodeMonitor.class.getSimpleName();
+
+ // To enable these logs, run:
+ // 'adb shell setprop log.tag.SysfsNodeMonitor DEBUG' (requires restart)
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final long SYSFS_NODE_MONITORING_TIMEOUT_MS = 60_000; // 1 minute
+
+ private final Context mContext;
+ private final NativeInputManagerService mNative;
+ private final Handler mHandler;
+ private final UEventManager mUEventManager;
+
+ private InputManager mInputManager;
+
+ private final SparseArray<SysfsNodeAddedListener> mUEventListenersByDeviceId =
+ new SparseArray<>();
+
+ SysfsNodeMonitor(Context context, NativeInputManagerService nativeService, Looper looper,
+ UEventManager uEventManager) {
+ mContext = context;
+ mNative = nativeService;
+ mHandler = new Handler(looper);
+ mUEventManager = uEventManager;
+ }
+
+ public void systemRunning() {
+ mInputManager = Objects.requireNonNull(mContext.getSystemService(InputManager.class));
+ mInputManager.registerInputDeviceListener(mInputDeviceListener, mHandler);
+ for (int deviceId : mInputManager.getInputDeviceIds()) {
+ mInputDeviceListener.onInputDeviceAdded(deviceId);
+ }
+ }
+
+ private final InputManager.InputDeviceListener mInputDeviceListener =
+ new InputManager.InputDeviceListener() {
+ @Override
+ public void onInputDeviceAdded(int deviceId) {
+ startMonitoring(deviceId);
+ }
+
+ @Override
+ public void onInputDeviceRemoved(int deviceId) {
+ stopMonitoring(deviceId);
+ }
+
+ @Override
+ public void onInputDeviceChanged(int deviceId) {
+ }
+ };
+
+ private void startMonitoring(int deviceId) {
+ final var inputDevice = mInputManager.getInputDevice(deviceId);
+ if (inputDevice == null) {
+ return;
+ }
+ if (!inputDevice.isExternal()) {
+ if (DEBUG) {
+ Log.d(TAG, "Not listening to sysfs node changes for internal input device: "
+ + deviceId);
+ }
+ return;
+ }
+ final var sysfsRootPath = formatDevPath(mNative.getSysfsRootPath(deviceId));
+ if (sysfsRootPath == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Sysfs node not found for external input device: " + deviceId);
+ }
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Start listening to sysfs node changes for input device: " + deviceId
+ + ", node: " + sysfsRootPath);
+ }
+ final var listener = new SysfsNodeAddedListener();
+ mUEventListenersByDeviceId.put(deviceId, listener);
+
+ // We must synchronously start monitoring for changes to this device's path.
+ // Once monitoring starts, we need to trigger a native refresh of the sysfs nodes to
+ // catch any changes that happened between the input device's creation and the UEvent
+ // listener being added.
+ // NOTE: This relies on the fact that the following `addListener` call is fully synchronous.
+ mUEventManager.addListener(listener, sysfsRootPath);
+ mNative.sysfsNodeChanged(sysfsRootPath);
+
+ // Always stop listening for new sysfs nodes after the timeout.
+ mHandler.postDelayed(() -> stopMonitoring(deviceId), SYSFS_NODE_MONITORING_TIMEOUT_MS);
+ }
+
+ private static String formatDevPath(String path) {
+ // Remove the "/sys" prefix if it has one.
+ return path != null && path.startsWith("/sys") ? path.substring(4) : path;
+ }
+
+ private void stopMonitoring(int deviceId) {
+ final var listener = mUEventListenersByDeviceId.removeReturnOld(deviceId);
+ if (listener == null) {
+ return;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Stop listening to sysfs node changes for input device: " + deviceId);
+ }
+ mUEventManager.removeListener(listener);
+ }
+
+ class SysfsNodeAddedListener extends UEventManager.UEventListener {
+
+ private boolean mHasReceivedRemovalNotification = false;
+ private boolean mHasReceivedPowerSupplyNotification = false;
+
+ @Override
+ public void onUEvent(UEventObserver.UEvent event) {
+ // This callback happens on the UEventObserver's thread.
+ // Ensure we are processing on the handler thread.
+ mHandler.post(() -> handleUEvent(event));
+ }
+
+ private void handleUEvent(UEventObserver.UEvent event) {
+ if (DEBUG) {
+ Slog.d(TAG, "UEventListener: Received UEvent: " + event);
+ }
+ final var subsystem = event.get("SUBSYSTEM");
+ final var devPath = "/sys" + Objects.requireNonNull(
+ TextUtils.nullIfEmpty(event.get("DEVPATH")));
+ final var action = event.get("ACTION");
+
+ // NOTE: We must be careful to avoid reconfiguring sysfs nodes during device removal,
+ // because it might result in the device getting re-opened in native code during
+ // removal, resulting in unexpected states. If we see any removal action for this node,
+ // ensure we stop responding altogether.
+ if (mHasReceivedRemovalNotification || "REMOVE".equalsIgnoreCase(action)) {
+ mHasReceivedRemovalNotification = true;
+ return;
+ }
+
+ if ("LEDS".equalsIgnoreCase(subsystem) && "ADD".equalsIgnoreCase(action)) {
+ // An LED node was added. Notify native code to reconfigure the sysfs node.
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Reconfiguring sysfs node because 'leds' node was added: " + devPath);
+ }
+ mNative.sysfsNodeChanged(devPath);
+ return;
+ }
+
+ if ("POWER_SUPPLY".equalsIgnoreCase(subsystem)) {
+ if (mHasReceivedPowerSupplyNotification) {
+ return;
+ }
+ // This is the first notification we received from the power_supply subsystem.
+ // Notify native code that the battery node may have been added. The power_supply
+ // subsystem does not seem to be sending ADD events, so use use the first event
+ // with any action as a proxy for a new power_supply node being created.
+ if (DEBUG) {
+ Slog.d(TAG, "Reconfiguring sysfs node because 'power_supply' node had action '"
+ + action + "': " + devPath);
+ }
+ mHasReceivedPowerSupplyNotification = true;
+ mNative.sysfsNodeChanged(devPath);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 87259d80554f..5e3224d1012e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -1826,14 +1826,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
@NonNull UserData userData) {
final int userId = userData.mUserId;
if (userData.mCurClient == client) {
- if (Flags.refactorInsetsController()) {
- final var statsToken = createStatsTokenForFocusedClient(false /* show */,
- SoftInputShowHideReason.HIDE_REMOVE_CLIENT, userId);
- setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
- } else {
- hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
- SoftInputShowHideReason.HIDE_REMOVE_CLIENT, userId);
- }
+ hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
+ SoftInputShowHideReason.HIDE_REMOVE_CLIENT, userId);
if (userData.mBoundToMethod) {
userData.mBoundToMethod = false;
final var userBindingController = userData.mBindingController;
@@ -2103,14 +2097,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
}
if (visibilityStateComputer.getImePolicy().isImeHiddenByDisplayPolicy()) {
- if (Flags.refactorInsetsController()) {
- final var statsToken = createStatsTokenForFocusedClient(false /* show */,
- SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE, userId);
- setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
- } else {
- hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
- SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE, userId);
- }
+ hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
+ SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE, userId);
return InputBindResult.NO_IME;
}
@@ -3867,17 +3855,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
+ " a background user, use EditorInfo.targetInputMethodUser with"
+ " INTERACT_ACROSS_USERS_FULL permission.");
-
- if (Flags.refactorInsetsController()) {
- final var statsToken = createStatsTokenForFocusedClient(
- false /* show */, SoftInputShowHideReason.HIDE_INVALID_USER,
- userId);
- setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
- } else {
- hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
- 0 /* flags */, SoftInputShowHideReason.HIDE_INVALID_USER,
- userId);
- }
+ hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
+ 0 /* flags */, SoftInputShowHideReason.HIDE_INVALID_USER, userId);
return InputBindResult.INVALID_USER;
}
@@ -5014,6 +4993,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
setImeVisibilityOnFocusedWindowClient(false, userData,
null /* TODO(b/353463205) check statsToken */);
} else {
+
hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
0 /* flags */, reason, userId);
}
@@ -6709,9 +6689,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final var userData = getUserData(userId);
if (Flags.refactorInsetsController()) {
- final var statsToken = createStatsTokenForFocusedClient(false /* show */,
- SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND, userId);
- setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
+ setImeVisibilityOnFocusedWindowClient(false, userData,
+ null /* TODO(b329229469) initialize statsToken here? */);
} else {
hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
0 /* flags */,
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index 0202b554b8aa..2152f76395a9 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -40,6 +40,7 @@ import android.hardware.tv.mediaquality.PictureParameter;
import android.hardware.tv.mediaquality.PictureParameters;
import android.hardware.tv.mediaquality.SoundParameter;
import android.hardware.tv.mediaquality.SoundParameters;
+import android.hardware.tv.mediaquality.StreamStatus;
import android.hardware.tv.mediaquality.VendorParamCapability;
import android.media.quality.AmbientBacklightEvent;
import android.media.quality.AmbientBacklightMetadata;
@@ -129,6 +130,9 @@ public class MediaQualityService extends SystemService {
// A global lock for ambient backlight objects.
private final Object mAmbientBacklightLock = new Object();
+ private final Map<Long, PictureProfile> mHandleToPictureProfile = new HashMap<>();
+ private final BiMap<Long, Long> mCurrentPictureHandleToOriginal = new BiMap<>();
+
public MediaQualityService(Context context) {
super(context);
mContext = context;
@@ -230,21 +234,24 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mPictureProfileLock")
@Override
public void updatePictureProfile(String id, PictureProfile pp, int userId) {
- Long dbId = mPictureProfileTempIdMap.getKey(id);
- if (!hasPermissionToUpdatePictureProfile(dbId, pp)) {
- mMqManagerNotifier.notifyOnPictureProfileError(id,
- PictureProfile.ERROR_NO_PERMISSION,
- Binder.getCallingUid(), Binder.getCallingPid());
- }
- synchronized (mPictureProfileLock) {
- ContentValues values = MediaQualityUtils.getContentValues(dbId,
- pp.getProfileType(),
- pp.getName(),
- pp.getPackageName(),
- pp.getInputId(),
- pp.getParameters());
- updateDatabaseOnPictureProfileAndNotifyManagerAndHal(values, pp.getParameters());
- }
+ mHandler.post(() -> {
+ Long dbId = mPictureProfileTempIdMap.getKey(id);
+ if (!hasPermissionToUpdatePictureProfile(dbId, pp)) {
+ mMqManagerNotifier.notifyOnPictureProfileError(id,
+ PictureProfile.ERROR_NO_PERMISSION,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ }
+ synchronized (mPictureProfileLock) {
+ ContentValues values = MediaQualityUtils.getContentValues(dbId,
+ pp.getProfileType(),
+ pp.getName(),
+ pp.getPackageName(),
+ pp.getInputId(),
+ pp.getParameters());
+ updateDatabaseOnPictureProfileAndNotifyManagerAndHal(values,
+ pp.getParameters());
+ }
+ });
}
private boolean hasPermissionToUpdatePictureProfile(Long dbId, PictureProfile toUpdate) {
@@ -258,35 +265,37 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mPictureProfileLock")
@Override
public void removePictureProfile(String id, int userId) {
- synchronized (mPictureProfileLock) {
- Long dbId = mPictureProfileTempIdMap.getKey(id);
+ mHandler.post(() -> {
+ synchronized (mPictureProfileLock) {
+ Long dbId = mPictureProfileTempIdMap.getKey(id);
- PictureProfile toDelete = mMqDatabaseUtils.getPictureProfile(dbId);
- if (!hasPermissionToRemovePictureProfile(toDelete)) {
- mMqManagerNotifier.notifyOnPictureProfileError(id,
- PictureProfile.ERROR_NO_PERMISSION,
- Binder.getCallingUid(), Binder.getCallingPid());
- }
-
- if (dbId != null) {
- SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
- String selection = BaseParameters.PARAMETER_ID + " = ?";
- String[] selectionArgs = {Long.toString(dbId)};
- int result = db.delete(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
- selection, selectionArgs);
- if (result == 0) {
+ PictureProfile toDelete = mMqDatabaseUtils.getPictureProfile(dbId);
+ if (!hasPermissionToRemovePictureProfile(toDelete)) {
mMqManagerNotifier.notifyOnPictureProfileError(id,
- PictureProfile.ERROR_INVALID_ARGUMENT,
- Binder.getCallingUid(), Binder.getCallingPid());
- } else {
- mMqManagerNotifier.notifyOnPictureProfileRemoved(
- mPictureProfileTempIdMap.getValue(dbId), toDelete,
+ PictureProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
- mPictureProfileTempIdMap.remove(dbId);
- mHalNotifier.notifyHalOnPictureProfileChange(dbId, null);
+ }
+
+ if (dbId != null) {
+ SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
+ String selection = BaseParameters.PARAMETER_ID + " = ?";
+ String[] selectionArgs = {Long.toString(dbId)};
+ int result = db.delete(mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
+ selection, selectionArgs);
+ if (result == 0) {
+ mMqManagerNotifier.notifyOnPictureProfileError(id,
+ PictureProfile.ERROR_INVALID_ARGUMENT,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ } else {
+ mMqManagerNotifier.notifyOnPictureProfileRemoved(
+ mPictureProfileTempIdMap.getValue(dbId), toDelete,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ mPictureProfileTempIdMap.remove(dbId);
+ mHalNotifier.notifyHalOnPictureProfileChange(dbId, null);
+ }
}
}
- }
+ });
}
private boolean hasPermissionToRemovePictureProfile(PictureProfile toDelete) {
@@ -368,13 +377,18 @@ public class MediaQualityService extends SystemService {
Binder.getCallingUid(), Binder.getCallingPid());
}
- PictureProfile pictureProfile = mMqDatabaseUtils.getPictureProfile(
- mPictureProfileTempIdMap.getKey(profileId));
+ Long longId = mPictureProfileTempIdMap.getKey(profileId);
+ if (longId == null) {
+ return false;
+ }
+ PictureProfile pictureProfile = mMqDatabaseUtils.getPictureProfile(longId);
PersistableBundle params = pictureProfile.getParameters();
try {
if (mMediaQuality != null) {
PictureParameters pp = new PictureParameters();
+ // put ID in params for profile update in HAL
+ params.putLong(BaseParameters.PARAMETER_ID, longId);
PictureParameter[] pictureParameters = MediaQualityUtils
.convertPersistableBundleToPictureParameterList(params);
@@ -429,6 +443,7 @@ public class MediaQualityService extends SystemService {
return toReturn;
}
+
@GuardedBy("mSoundProfileLock")
@Override
public List<SoundProfileHandle> getSoundProfileHandle(String[] ids, int userId) {
@@ -448,56 +463,60 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mSoundProfileLock")
@Override
- public SoundProfile createSoundProfile(SoundProfile sp, int userId) {
- if ((sp.getPackageName() != null && !sp.getPackageName().isEmpty()
- && !incomingPackageEqualsCallingUidPackage(sp.getPackageName()))
- && !hasGlobalSoundQualityServicePermission()) {
- mMqManagerNotifier.notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
- Binder.getCallingUid(), Binder.getCallingPid());
- }
-
- synchronized (mSoundProfileLock) {
- SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
+ public void createSoundProfile(SoundProfile sp, int userId) {
+ mHandler.post(() -> {
+ if ((sp.getPackageName() != null && !sp.getPackageName().isEmpty()
+ && !incomingPackageEqualsCallingUidPackage(sp.getPackageName()))
+ && !hasGlobalSoundQualityServicePermission()) {
+ mMqManagerNotifier.notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ }
- ContentValues values = MediaQualityUtils.getContentValues(null,
- sp.getProfileType(),
- sp.getName(),
- sp.getPackageName() == null || sp.getPackageName().isEmpty()
- ? getPackageOfCallingUid() : sp.getPackageName(),
- sp.getInputId(),
- sp.getParameters());
+ synchronized (mSoundProfileLock) {
+ SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
- // id is auto-generated by SQLite upon successful insertion of row
- Long id = db.insert(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
- null, values);
- MediaQualityUtils.populateTempIdMap(mSoundProfileTempIdMap, id);
- String value = mSoundProfileTempIdMap.getValue(id);
- sp.setProfileId(value);
- mMqManagerNotifier.notifyOnSoundProfileAdded(value, sp, Binder.getCallingUid(),
- Binder.getCallingPid());
- return sp;
- }
+ ContentValues values = MediaQualityUtils.getContentValues(null,
+ sp.getProfileType(),
+ sp.getName(),
+ sp.getPackageName() == null || sp.getPackageName().isEmpty()
+ ? getPackageOfCallingUid() : sp.getPackageName(),
+ sp.getInputId(),
+ sp.getParameters());
+
+ // id is auto-generated by SQLite upon successful insertion of row
+ Long id = db.insert(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
+ null, values);
+ MediaQualityUtils.populateTempIdMap(mSoundProfileTempIdMap, id);
+ String value = mSoundProfileTempIdMap.getValue(id);
+ sp.setProfileId(value);
+ mMqManagerNotifier.notifyOnSoundProfileAdded(value, sp, Binder.getCallingUid(),
+ Binder.getCallingPid());
+ }
+ });
}
@GuardedBy("mSoundProfileLock")
@Override
public void updateSoundProfile(String id, SoundProfile sp, int userId) {
- Long dbId = mSoundProfileTempIdMap.getKey(id);
- if (!hasPermissionToUpdateSoundProfile(dbId, sp)) {
- mMqManagerNotifier.notifyOnSoundProfileError(id, SoundProfile.ERROR_NO_PERMISSION,
- Binder.getCallingUid(), Binder.getCallingPid());
- }
+ mHandler.post(() -> {
+ Long dbId = mSoundProfileTempIdMap.getKey(id);
+ if (!hasPermissionToUpdateSoundProfile(dbId, sp)) {
+ mMqManagerNotifier.notifyOnSoundProfileError(id,
+ SoundProfile.ERROR_NO_PERMISSION,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ }
- synchronized (mSoundProfileLock) {
- ContentValues values = MediaQualityUtils.getContentValues(dbId,
- sp.getProfileType(),
- sp.getName(),
- sp.getPackageName(),
- sp.getInputId(),
- sp.getParameters());
+ synchronized (mSoundProfileLock) {
+ ContentValues values = MediaQualityUtils.getContentValues(dbId,
+ sp.getProfileType(),
+ sp.getName(),
+ sp.getPackageName(),
+ sp.getInputId(),
+ sp.getParameters());
- updateDatabaseOnSoundProfileAndNotifyManagerAndHal(values, sp.getParameters());
- }
+ updateDatabaseOnSoundProfileAndNotifyManagerAndHal(values, sp.getParameters());
+ }
+ });
}
private boolean hasPermissionToUpdateSoundProfile(Long dbId, SoundProfile sp) {
@@ -511,34 +530,36 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mSoundProfileLock")
@Override
public void removeSoundProfile(String id, int userId) {
- synchronized (mSoundProfileLock) {
- Long dbId = mSoundProfileTempIdMap.getKey(id);
- SoundProfile toDelete = mMqDatabaseUtils.getSoundProfile(dbId);
- if (!hasPermissionToRemoveSoundProfile(toDelete)) {
- mMqManagerNotifier.notifyOnSoundProfileError(id,
- SoundProfile.ERROR_NO_PERMISSION,
- Binder.getCallingUid(), Binder.getCallingPid());
- }
- if (dbId != null) {
- SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
- String selection = BaseParameters.PARAMETER_ID + " = ?";
- String[] selectionArgs = {Long.toString(dbId)};
- int result = db.delete(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
- selection,
- selectionArgs);
- if (result == 0) {
+ mHandler.post(() -> {
+ synchronized (mSoundProfileLock) {
+ Long dbId = mSoundProfileTempIdMap.getKey(id);
+ SoundProfile toDelete = mMqDatabaseUtils.getSoundProfile(dbId);
+ if (!hasPermissionToRemoveSoundProfile(toDelete)) {
mMqManagerNotifier.notifyOnSoundProfileError(id,
- SoundProfile.ERROR_INVALID_ARGUMENT,
- Binder.getCallingUid(), Binder.getCallingPid());
- } else {
- mMqManagerNotifier.notifyOnSoundProfileRemoved(
- mSoundProfileTempIdMap.getValue(dbId), toDelete,
+ SoundProfile.ERROR_NO_PERMISSION,
Binder.getCallingUid(), Binder.getCallingPid());
- mSoundProfileTempIdMap.remove(dbId);
- mHalNotifier.notifyHalOnSoundProfileChange(dbId, null);
+ }
+ if (dbId != null) {
+ SQLiteDatabase db = mMediaQualityDbHelper.getWritableDatabase();
+ String selection = BaseParameters.PARAMETER_ID + " = ?";
+ String[] selectionArgs = {Long.toString(dbId)};
+ int result = db.delete(mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
+ selection,
+ selectionArgs);
+ if (result == 0) {
+ mMqManagerNotifier.notifyOnSoundProfileError(id,
+ SoundProfile.ERROR_INVALID_ARGUMENT,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ } else {
+ mMqManagerNotifier.notifyOnSoundProfileRemoved(
+ mSoundProfileTempIdMap.getValue(dbId), toDelete,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ mSoundProfileTempIdMap.remove(dbId);
+ mHalNotifier.notifyHalOnSoundProfileChange(dbId, null);
+ }
}
}
- }
+ });
}
private boolean hasPermissionToRemoveSoundProfile(SoundProfile toDelete) {
@@ -619,12 +640,18 @@ public class MediaQualityService extends SystemService {
Binder.getCallingUid(), Binder.getCallingPid());
}
- SoundProfile soundProfile =
- mMqDatabaseUtils.getSoundProfile(mSoundProfileTempIdMap.getKey(profileId));
+ Long longId = mSoundProfileTempIdMap.getKey(profileId);
+ if (longId == null) {
+ return false;
+ }
+
+ SoundProfile soundProfile = mMqDatabaseUtils.getSoundProfile(longId);
PersistableBundle params = soundProfile.getParameters();
try {
if (mMediaQuality != null) {
+ // put ID in params for profile update in HAL
+ params.putLong(BaseParameters.PARAMETER_ID, longId);
SoundParameter[] soundParameters =
MediaQualityUtils.convertPersistableBundleToSoundParameterList(params);
@@ -738,6 +765,26 @@ public class MediaQualityService extends SystemService {
}
}
+ public void unregisterAmbientBacklightCallback(IAmbientBacklightCallback callback) {
+ if (DEBUG) {
+ Slogf.d(TAG, "unregisterAmbientBacklightCallback");
+ }
+
+ if (!hasReadColorZonesPermission()) {
+ //TODO: error handling
+ }
+
+ synchronized (mCallbackRecords) {
+ for (AmbientBacklightCallbackRecord record : mCallbackRecords.values()) {
+ if (record.mCallback.asBinder().equals(callback.asBinder())) {
+ record.release();
+ mCallbackRecords.remove(record.mPackageName);
+ return;
+ }
+ }
+ }
+ }
+
@GuardedBy("mAmbientBacklightLock")
@Override
public void setAmbientBacklightSettings(
@@ -849,14 +896,16 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mPictureProfileLock")
@Override
public void setPictureProfileAllowList(List<String> packages, int userId) {
- if (!hasGlobalPictureQualityServicePermission()) {
- mMqManagerNotifier.notifyOnPictureProfileError(null,
- PictureProfile.ERROR_NO_PERMISSION,
- Binder.getCallingUid(), Binder.getCallingPid());
- }
- SharedPreferences.Editor editor = mPictureProfileSharedPreference.edit();
- editor.putString(ALLOWLIST, String.join(COMMA_DELIMITER, packages));
- editor.commit();
+ mHandler.post(() -> {
+ if (!hasGlobalPictureQualityServicePermission()) {
+ mMqManagerNotifier.notifyOnPictureProfileError(null,
+ PictureProfile.ERROR_NO_PERMISSION,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ }
+ SharedPreferences.Editor editor = mPictureProfileSharedPreference.edit();
+ editor.putString(ALLOWLIST, String.join(COMMA_DELIMITER, packages));
+ editor.commit();
+ });
}
@GuardedBy("mSoundProfileLock")
@@ -877,13 +926,16 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mSoundProfileLock")
@Override
public void setSoundProfileAllowList(List<String> packages, int userId) {
- if (!hasGlobalSoundQualityServicePermission()) {
- mMqManagerNotifier.notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
- Binder.getCallingUid(), Binder.getCallingPid());
- }
- SharedPreferences.Editor editor = mSoundProfileSharedPreference.edit();
- editor.putString(ALLOWLIST, String.join(COMMA_DELIMITER, packages));
- editor.commit();
+ mHandler.post(() -> {
+ if (!hasGlobalSoundQualityServicePermission()) {
+ mMqManagerNotifier.notifyOnSoundProfileError(null,
+ SoundProfile.ERROR_NO_PERMISSION,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ }
+ SharedPreferences.Editor editor = mSoundProfileSharedPreference.edit();
+ editor.putString(ALLOWLIST, String.join(COMMA_DELIMITER, packages));
+ editor.commit();
+ });
}
@Override
@@ -894,22 +946,24 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mPictureProfileLock")
@Override
public void setAutoPictureQualityEnabled(boolean enabled, int userId) {
- if (!hasGlobalPictureQualityServicePermission()) {
- mMqManagerNotifier.notifyOnPictureProfileError(null,
- PictureProfile.ERROR_NO_PERMISSION,
- Binder.getCallingUid(), Binder.getCallingPid());
- }
- synchronized (mPictureProfileLock) {
- try {
- if (mMediaQuality != null) {
- if (mMediaQuality.isAutoPqSupported()) {
- mMediaQuality.setAutoPqEnabled(enabled);
+ mHandler.post(() -> {
+ if (!hasGlobalPictureQualityServicePermission()) {
+ mMqManagerNotifier.notifyOnPictureProfileError(null,
+ PictureProfile.ERROR_NO_PERMISSION,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ }
+ synchronized (mPictureProfileLock) {
+ try {
+ if (mMediaQuality != null) {
+ if (mMediaQuality.isAutoPqSupported()) {
+ mMediaQuality.setAutoPqEnabled(enabled);
+ }
}
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set auto picture quality", e);
}
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to set auto picture quality", e);
}
- }
+ });
}
@GuardedBy("mPictureProfileLock")
@@ -932,22 +986,24 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mPictureProfileLock")
@Override
public void setSuperResolutionEnabled(boolean enabled, int userId) {
- if (!hasGlobalPictureQualityServicePermission()) {
- mMqManagerNotifier.notifyOnPictureProfileError(null,
- PictureProfile.ERROR_NO_PERMISSION,
- Binder.getCallingUid(), Binder.getCallingPid());
- }
- synchronized (mPictureProfileLock) {
- try {
- if (mMediaQuality != null) {
- if (mMediaQuality.isAutoSrSupported()) {
- mMediaQuality.setAutoSrEnabled(enabled);
+ mHandler.post(() -> {
+ if (!hasGlobalPictureQualityServicePermission()) {
+ mMqManagerNotifier.notifyOnPictureProfileError(null,
+ PictureProfile.ERROR_NO_PERMISSION,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ }
+ synchronized (mPictureProfileLock) {
+ try {
+ if (mMediaQuality != null) {
+ if (mMediaQuality.isAutoSrSupported()) {
+ mMediaQuality.setAutoSrEnabled(enabled);
+ }
}
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set super resolution", e);
}
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to set super resolution", e);
}
- }
+ });
}
@GuardedBy("mPictureProfileLock")
@@ -970,22 +1026,25 @@ public class MediaQualityService extends SystemService {
@GuardedBy("mSoundProfileLock")
@Override
public void setAutoSoundQualityEnabled(boolean enabled, int userId) {
- if (!hasGlobalSoundQualityServicePermission()) {
- mMqManagerNotifier.notifyOnSoundProfileError(null, SoundProfile.ERROR_NO_PERMISSION,
- Binder.getCallingUid(), Binder.getCallingPid());
- }
+ mHandler.post(() -> {
+ if (!hasGlobalSoundQualityServicePermission()) {
+ mMqManagerNotifier.notifyOnSoundProfileError(null,
+ SoundProfile.ERROR_NO_PERMISSION,
+ Binder.getCallingUid(), Binder.getCallingPid());
+ }
- synchronized (mSoundProfileLock) {
- try {
- if (mMediaQuality != null) {
- if (mMediaQuality.isAutoAqSupported()) {
- mMediaQuality.setAutoAqEnabled(enabled);
+ synchronized (mSoundProfileLock) {
+ try {
+ if (mMediaQuality != null) {
+ if (mMediaQuality.isAutoAqSupported()) {
+ mMediaQuality.setAutoAqEnabled(enabled);
+ }
}
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to set auto sound quality", e);
}
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to set auto sound quality", e);
}
- }
+ });
}
@GuardedBy("mSoundProfileLock")
@@ -1127,15 +1186,17 @@ public class MediaQualityService extends SystemService {
private final class MqDatabaseUtils {
private PictureProfile getPictureProfile(Long dbId) {
+ return getPictureProfile(dbId, false);
+ }
+
+ private PictureProfile getPictureProfile(Long dbId, boolean includeParams) {
String selection = BaseParameters.PARAMETER_ID + " = ?";
String[] selectionArguments = {Long.toString(dbId)};
- try (
- Cursor cursor = getCursorAfterQuerying(
- mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
- MediaQualityUtils.getMediaProfileColumns(false), selection,
- selectionArguments)
- ) {
+ try (Cursor cursor = getCursorAfterQuerying(
+ mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME,
+ MediaQualityUtils.getMediaProfileColumns(includeParams), selection,
+ selectionArguments)) {
int count = cursor.getCount();
if (count == 0) {
return null;
@@ -1154,11 +1215,9 @@ public class MediaQualityService extends SystemService {
private List<PictureProfile> getPictureProfilesBasedOnConditions(String[] columns,
String selection, String[] selectionArguments) {
- try (
- Cursor cursor = getCursorAfterQuerying(
- mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, columns, selection,
- selectionArguments)
- ) {
+ try (Cursor cursor = getCursorAfterQuerying(
+ mMediaQualityDbHelper.PICTURE_QUALITY_TABLE_NAME, columns, selection,
+ selectionArguments)) {
List<PictureProfile> pictureProfiles = new ArrayList<>();
while (cursor.moveToNext()) {
pictureProfiles.add(MediaQualityUtils.convertCursorToPictureProfileWithTempId(
@@ -1172,12 +1231,10 @@ public class MediaQualityService extends SystemService {
String selection = BaseParameters.PARAMETER_ID + " = ?";
String[] selectionArguments = {Long.toString(dbId)};
- try (
- Cursor cursor = mMqDatabaseUtils.getCursorAfterQuerying(
- mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
- MediaQualityUtils.getMediaProfileColumns(false), selection,
- selectionArguments)
- ) {
+ try (Cursor cursor = mMqDatabaseUtils.getCursorAfterQuerying(
+ mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME,
+ MediaQualityUtils.getMediaProfileColumns(false), selection,
+ selectionArguments)) {
int count = cursor.getCount();
if (count == 0) {
return null;
@@ -1196,11 +1253,9 @@ public class MediaQualityService extends SystemService {
private List<SoundProfile> getSoundProfilesBasedOnConditions(String[] columns,
String selection, String[] selectionArguments) {
- try (
- Cursor cursor = mMqDatabaseUtils.getCursorAfterQuerying(
- mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, columns, selection,
- selectionArguments)
- ) {
+ try (Cursor cursor = mMqDatabaseUtils.getCursorAfterQuerying(
+ mMediaQualityDbHelper.SOUND_QUALITY_TABLE_NAME, columns, selection,
+ selectionArguments)) {
List<SoundProfile> soundProfiles = new ArrayList<>();
while (cursor.moveToNext()) {
soundProfiles.add(MediaQualityUtils.convertCursorToSoundProfileWithTempId(
@@ -1416,8 +1471,19 @@ public class MediaQualityService extends SystemService {
private void notifyHalOnPictureProfileChange(Long dbId, PersistableBundle params) {
// TODO: only notify HAL when the profile is active / being used
if (mPpChangedListener != null) {
+ Long currentHandle = mCurrentPictureHandleToOriginal.getKey(dbId);
+ if (currentHandle != null) {
+ // this handle maps to another current profile, skip
+ return;
+ }
try {
- mPpChangedListener.onPictureProfileChanged(convertToHalPictureProfile(dbId,
+ Long idForHal = dbId;
+ Long originalHandle = mCurrentPictureHandleToOriginal.getValue(dbId);
+ if (originalHandle != null) {
+ // the original id is used in HAL because of status change
+ idForHal = originalHandle;
+ }
+ mPpChangedListener.onPictureProfileChanged(convertToHalPictureProfile(idForHal,
params));
} catch (RemoteException e) {
Slog.e(TAG, "Failed to notify HAL on picture profile change.", e);
@@ -1546,9 +1612,116 @@ public class MediaQualityService extends SystemService {
}
@Override
- public void onStreamStatusChanged(long pictureProfileId, byte status)
+ public void onStreamStatusChanged(long profileHandle, byte status)
throws RemoteException {
- // TODO
+ mHandler.post(() -> {
+ synchronized (mPictureProfileLock) {
+ // get from map if exists
+ PictureProfile previous = mHandleToPictureProfile.get(profileHandle);
+ if (previous == null) {
+ // get from DB if not exists
+ previous = mMqDatabaseUtils.getPictureProfile(profileHandle);
+ if (previous == null) {
+ return;
+ }
+ }
+ String[] arr = splitNameAndStatus(previous.getName());
+ String profileName = arr[0];
+ String profileStatus = arr[1];
+ if (status == StreamStatus.HDR10) {
+ if (isHdr(profileStatus)) {
+ // already HDR
+ return;
+ }
+ if (isSdr(profileStatus)) {
+ // SDR to HDR
+ String selection = BaseParameters.PARAMETER_TYPE + " = ? AND "
+ + BaseParameters.PARAMETER_PACKAGE + " = ? AND "
+ + BaseParameters.PARAMETER_NAME + " = ?";
+ String[] selectionArguments = {
+ Integer.toString(previous.getProfileType()),
+ previous.getPackageName(),
+ profileName + "/" + PictureProfile.STATUS_HDR
+ };
+ List<PictureProfile> list =
+ mMqDatabaseUtils.getPictureProfilesBasedOnConditions(
+ MediaQualityUtils.getMediaProfileColumns(true),
+ selection,
+ selectionArguments);
+ if (list.isEmpty()) {
+ // HDR profile not found
+ return;
+ }
+ PictureProfile current = list.get(0);
+ mHandleToPictureProfile.put(profileHandle, current);
+ mCurrentPictureHandleToOriginal.put(
+ current.getHandle().getId(), profileHandle);
+
+ mHalNotifier.notifyHalOnPictureProfileChange(profileHandle,
+ current.getParameters());
+
+ }
+ } else if (status == StreamStatus.SDR) {
+ if (isSdr(profileStatus)) {
+ // already SDR
+ return;
+ }
+ if (isHdr(profileStatus)) {
+ // HDR to SDR
+ String selection = BaseParameters.PARAMETER_TYPE + " = ? AND "
+ + BaseParameters.PARAMETER_PACKAGE + " = ? AND ("
+ + BaseParameters.PARAMETER_NAME + " = ? OR "
+ + BaseParameters.PARAMETER_NAME + " = ?)";
+ String[] selectionArguments = {
+ Integer.toString(previous.getProfileType()),
+ previous.getPackageName(),
+ profileName,
+ profileName + "/" + PictureProfile.STATUS_SDR
+ };
+ List<PictureProfile> list =
+ mMqDatabaseUtils.getPictureProfilesBasedOnConditions(
+ MediaQualityUtils.getMediaProfileColumns(true),
+ selection,
+ selectionArguments);
+ if (list.isEmpty()) {
+ // SDR profile not found
+ return;
+ }
+ PictureProfile current = list.get(0);
+ mHandleToPictureProfile.put(profileHandle, current);
+ mCurrentPictureHandleToOriginal.put(
+ current.getHandle().getId(), profileHandle);
+
+ mHalNotifier.notifyHalOnPictureProfileChange(profileHandle,
+ current.getParameters());
+ }
+ }
+ }
+ });
+
+ }
+
+ @NonNull
+ private String[] splitNameAndStatus(@NonNull String nameAndStatus) {
+ int index = nameAndStatus.lastIndexOf('/');
+ if (index == -1 || index == nameAndStatus.length() - 1) {
+ // no status in the original name
+ return new String[] {nameAndStatus, ""};
+ }
+ return new String[] {
+ nameAndStatus.substring(0, index),
+ nameAndStatus.substring(index + 1)
+ };
+
+ }
+
+ private boolean isSdr(@NonNull String profileStatus) {
+ return profileStatus.equals(PictureProfile.STATUS_SDR)
+ || profileStatus.isEmpty();
+ }
+
+ private boolean isHdr(@NonNull String profileStatus) {
+ return profileStatus.equals(PictureProfile.STATUS_HDR);
}
@Override
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityUtils.java b/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
index 05aac5587c2c..08a0b595033c 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityUtils.java
@@ -1273,14 +1273,18 @@ public final class MediaQualityUtils {
*/
public static PictureProfile convertCursorToPictureProfileWithTempId(Cursor cursor,
BiMap<Long, String> map) {
+ String tmpId = getTempId(map, cursor);
+ Long dbId = map.getKey(tmpId);
+ PictureProfileHandle handle = dbId == null
+ ? PictureProfileHandle.NONE : new PictureProfileHandle(dbId);
return new PictureProfile(
- getTempId(map, cursor),
+ tmpId,
getType(cursor),
getName(cursor),
getInputId(cursor),
getPackageName(cursor),
jsonToPersistableBundle(getSettingsString(cursor)),
- PictureProfileHandle.NONE
+ handle
);
}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 0ea9af4b9c38..e1e8fc231dda 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1712,15 +1712,19 @@ public class UserManagerService extends IUserManager.Stub {
@Override
public int getCredentialOwnerProfile(@UserIdInt int userId) {
checkManageUsersPermission("get the credential owner");
- if (!mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
- synchronized (mUsersLock) {
- UserInfo profileParent = getProfileParentLU(userId);
- if (profileParent != null) {
- return profileParent.id;
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (!mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)) {
+ synchronized (mUsersLock) {
+ UserInfo profileParent = getProfileParentLU(userId);
+ if (profileParent != null) {
+ return profileParent.id;
+ }
}
}
+ } finally {
+ Binder.restoreCallingIdentity(identity);
}
-
return userId;
}
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 8cf0481b1dc3..e8843ac214ec 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -520,32 +520,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private WindowWakeUpPolicy mWindowWakeUpPolicy;
- /**
- * The three variables below are used for custom power key gesture detection in
- * PhoneWindowManager. They are used to detect when the power button has been double pressed
- * and, when it does happen, makes the behavior overrideable by the app.
- *
- * We cannot use the {@link PowerKeyRule} for this because multi-press power gesture detection
- * and behaviors are handled by {@link com.android.server.GestureLauncherService}, and the
- * {@link PowerKeyRule} only handles single and long-presses of the power button. As a result,
- * overriding the double tap behavior requires custom gesture detection here that mimics the
- * logic in {@link com.android.server.GestureLauncherService}.
- *
- * Long-term, it would be beneficial to move all power gesture detection to
- * {@link PowerKeyRule} so that this custom logic isn't required.
- */
- // Time of last power down event.
- private long mLastPowerDown;
-
- // Number of power button events consecutively triggered (within a specific timeout threshold).
- private int mPowerButtonConsecutiveTaps = 0;
-
- // Whether a double tap of the power button has been detected.
- volatile boolean mDoubleTapPowerDetected;
-
- // Runnable that is queued on a delay when the first power keyDown event is sent to the app.
- private Runnable mPowerKeyDelayedRunnable = null;
-
boolean mSafeMode;
// Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key.
@@ -1135,10 +1109,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
|| handledByPowerManager || isKeyGestureTriggered
|| mKeyCombinationManager.isPowerKeyIntercepted();
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- mPowerKeyHandled |= mDoubleTapPowerDetected;
- }
-
if (!mPowerKeyHandled) {
if (!interactive) {
wakeUpFromWakeKey(event);
@@ -2785,18 +2755,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
if (mShouldEarlyShortPressOnPower) {
return;
}
- // TODO(b/380433365): Remove deferring single power press action when refactoring.
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- mDeferredKeyActionExecutor.cancelQueuedAction(KEYCODE_POWER);
- mDeferredKeyActionExecutor.queueKeyAction(
- KEYCODE_POWER,
- downTime,
- () -> {
- powerPress(downTime, 1 /*count*/, displayId);
- });
- } else {
- powerPress(downTime, 1 /*count*/, displayId);
- }
+ powerPress(downTime, 1 /*count*/, displayId);
}
@Override
@@ -2827,17 +2786,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@Override
void onMultiPress(long downTime, int count, int displayId) {
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- mDeferredKeyActionExecutor.cancelQueuedAction(KEYCODE_POWER);
- mDeferredKeyActionExecutor.queueKeyAction(
- KEYCODE_POWER,
- downTime,
- () -> {
- powerPress(downTime, count, displayId);
- });
- } else {
- powerPress(downTime, count, displayId);
- }
+ powerPress(downTime, count, displayId);
}
@Override
@@ -3614,12 +3563,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
}
- if (overridePowerKeyBehaviorInFocusedWindow() && event.getKeyCode() == KEYCODE_POWER
- && event.getAction() == KeyEvent.ACTION_UP
- && mDoubleTapPowerDetected) {
- mDoubleTapPowerDetected = false;
- }
-
return needToConsumeKey ? keyConsumed : keyNotConsumed;
}
@@ -4117,8 +4060,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
sendSystemKeyToStatusBarAsync(event);
return true;
}
- case KeyEvent.KEYCODE_POWER:
- return interceptPowerKeyBeforeDispatching(focusedToken, event);
case KeyEvent.KEYCODE_SCREENSHOT:
if (firstDown) {
interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
@@ -4174,8 +4115,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
sendSystemKeyToStatusBarAsync(event);
return true;
}
- case KeyEvent.KEYCODE_POWER:
- return interceptPowerKeyBeforeDispatching(focusedToken, event);
}
if (isValidGlobalKey(keyCode)
&& mGlobalKeyManager.handleGlobalKey(mContext, keyCode, event)) {
@@ -4193,90 +4132,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return (metaState & KeyEvent.META_META_ON) != 0;
}
- /**
- * Called by interceptKeyBeforeDispatching to handle interception logic for KEYCODE_POWER
- * KeyEvents.
- *
- * @return true if intercepting the key, false if sending to app.
- */
- private boolean interceptPowerKeyBeforeDispatching(IBinder focusedToken, KeyEvent event) {
- if (!overridePowerKeyBehaviorInFocusedWindow()) {
- //Flag disabled: intercept the power key and do not send to app.
- return true;
- }
- if (event.getKeyCode() != KEYCODE_POWER) {
- Log.wtf(TAG, "interceptPowerKeyBeforeDispatching received a non-power KeyEvent "
- + "with key code: " + event.getKeyCode());
- return false;
- }
-
- // Intercept keys (don't send to app) for 3x, 4x, 5x gestures)
- if (mPowerButtonConsecutiveTaps > DOUBLE_POWER_TAP_COUNT_THRESHOLD) {
- setDeferredKeyActionsExecutableAsync(KEYCODE_POWER, event.getDownTime());
- return true;
- }
-
- // UP key; just reuse the original decision.
- if (event.getAction() == KeyEvent.ACTION_UP) {
- final Set<Integer> consumedKeys = mConsumedKeysForDevice.get(event.getDeviceId());
- return consumedKeys != null
- && consumedKeys.contains(event.getKeyCode());
- }
-
- KeyInterceptionInfo info =
- mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken);
-
- if (info == null || !mButtonOverridePermissionChecker.canWindowOverridePowerKey(mContext,
- info.windowOwnerUid, info.inputFeaturesFlags)) {
- // The focused window does not have the permission to override power key behavior.
- if (DEBUG_INPUT) {
- String interceptReason = "";
- if (info == null) {
- interceptReason = "Window is null";
- } else if (!mButtonOverridePermissionChecker.canAppOverrideSystemKey(mContext,
- info.windowOwnerUid)) {
- interceptReason = "Application does not have "
- + "OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW permission";
- } else {
- interceptReason = "Window does not have inputFeatureFlag set";
- }
-
- Log.d(TAG, TextUtils.formatSimple("Intercepting KEYCODE_POWER event. action=%d, "
- + "eventTime=%d to window=%s. interceptReason=%s. "
- + "mDoubleTapPowerDetected=%b",
- event.getAction(), event.getEventTime(), (info != null)
- ? info.windowTitle : "null", interceptReason,
- mDoubleTapPowerDetected));
- }
- // Intercept the key (i.e. do not send to app)
- setDeferredKeyActionsExecutableAsync(KEYCODE_POWER, event.getDownTime());
- return true;
- }
-
- if (DEBUG_INPUT) {
- Log.d(TAG, TextUtils.formatSimple("Sending KEYCODE_POWER to app. action=%d, "
- + "eventTime=%d to window=%s. mDoubleTapPowerDetected=%b",
- event.getAction(), event.getEventTime(), info.windowTitle,
- mDoubleTapPowerDetected));
- }
-
- if (!mDoubleTapPowerDetected) {
- //Single press: post a delayed runnable for the single press power action that will be
- // called if it's not cancelled by a double press.
- final var downTime = event.getDownTime();
- mPowerKeyDelayedRunnable = () ->
- setDeferredKeyActionsExecutableAsync(KEYCODE_POWER, downTime);
- mHandler.postDelayed(mPowerKeyDelayedRunnable, POWER_MULTI_PRESS_TIMEOUT_MILLIS);
- } else if (mPowerKeyDelayedRunnable != null) {
- //Double press detected: cancel the single press runnable.
- mHandler.removeCallbacks(mPowerKeyDelayedRunnable);
- mPowerKeyDelayedRunnable = null;
- }
-
- // Focused window has permission. Send to app.
- return false;
- }
-
@SuppressLint("MissingPermission")
private void initKeyGestures() {
if (!useKeyGestureEventHandler()) {
@@ -4764,11 +4619,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return true;
}
- if (overridePowerKeyBehaviorInFocusedWindow() && keyCode == KEYCODE_POWER) {
- handleUnhandledSystemKey(event);
- return true;
- }
-
if (useKeyGestureEventHandler()) {
return false;
}
@@ -5595,12 +5445,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
KeyEvent.actionToString(event.getAction()),
mPowerKeyHandled ? 1 : 0,
mSingleKeyGestureDetector.getKeyPressCounter(KeyEvent.KEYCODE_POWER));
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- result |= ACTION_PASS_TO_USER;
- } else {
- // Any activity on the power button stops the accessibility shortcut
- result &= ~ACTION_PASS_TO_USER;
- }
+ // Any activity on the power button stops the accessibility shortcut
+ result &= ~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-up will be handled separately
if (down) {
interceptPowerKeyDown(event, interactiveAndAwake, isKeyGestureTriggered);
@@ -5862,35 +5708,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
if (event.getKeyCode() == KEYCODE_POWER && event.getAction() == KeyEvent.ACTION_DOWN) {
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- if (event.getRepeatCount() > 0 && !mHasFeatureWatch) {
- return;
- }
- if (mGestureLauncherService != null) {
- mGestureLauncherService.processPowerKeyDown(event);
- }
-
- if (detectDoubleTapPower(event)) {
- mDoubleTapPowerDetected = true;
-
- // Copy of the event for handler in case the original event gets recycled.
- KeyEvent eventCopy = KeyEvent.obtain(event);
- mDeferredKeyActionExecutor.queueKeyAction(
- KeyEvent.KEYCODE_POWER,
- eventCopy.getEventTime(),
- () -> {
- if (!handleCameraGesture(eventCopy, interactive)) {
- mSingleKeyGestureDetector.interceptKey(
- eventCopy, interactive, defaultDisplayOn);
- } else {
- mSingleKeyGestureDetector.reset();
- }
- eventCopy.recycle();
- });
- return;
- }
- }
-
mPowerKeyHandled = handleCameraGesture(event, interactive);
if (mPowerKeyHandled) {
// handled by camera gesture.
@@ -5902,26 +5719,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
mSingleKeyGestureDetector.interceptKey(event, interactive, defaultDisplayOn);
}
- private boolean detectDoubleTapPower(KeyEvent event) {
- //Watches use the SingleKeyGestureDetector for detecting multi-press gestures.
- if (mHasFeatureWatch || event.getKeyCode() != KEYCODE_POWER
- || event.getAction() != KeyEvent.ACTION_DOWN || event.getRepeatCount() != 0) {
- return false;
- }
-
- final long powerTapInterval = event.getEventTime() - mLastPowerDown;
- mLastPowerDown = event.getEventTime();
- if (powerTapInterval >= POWER_MULTI_PRESS_TIMEOUT_MILLIS) {
- // Tap too slow for double press
- mPowerButtonConsecutiveTaps = 1;
- } else {
- mPowerButtonConsecutiveTaps++;
- }
-
- return powerTapInterval < POWER_MULTI_PRESS_TIMEOUT_MILLIS
- && mPowerButtonConsecutiveTaps == DOUBLE_POWER_TAP_COUNT_THRESHOLD;
- }
-
// The camera gesture will be detected by GestureLauncherService.
private boolean handleCameraGesture(KeyEvent event, boolean interactive) {
// camera gesture.
@@ -7779,12 +7576,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
null)
== PERMISSION_GRANTED;
}
-
- boolean canWindowOverridePowerKey(Context context, int uid, int inputFeaturesFlags) {
- return canAppOverrideSystemKey(context, uid)
- && (inputFeaturesFlags & WindowManager.LayoutParams
- .INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS) != 0;
- }
}
private int getTargetDisplayIdForKeyEvent(KeyEvent event) {
diff --git a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
index 22a359bced86..c1d1e2ba3e76 100644
--- a/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
+++ b/services/core/java/com/android/server/security/AttestationVerificationManagerService.java
@@ -99,11 +99,6 @@ public class AttestationVerificationManagerService extends SystemService {
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
@Nullable String[] args) {
- if (!android.security.Flags.dumpAttestationVerifications()) {
- super.dump(fd, writer, args);
- return;
- }
-
if (!DumpUtils.checkDumpAndUsageStatsPermission(getContext(), TAG, writer)) return;
final IndentingPrintWriter fout = new IndentingPrintWriter(writer, " ");
diff --git a/services/core/java/com/android/server/tv/TvInputHal.java b/services/core/java/com/android/server/tv/TvInputHal.java
index 87ebdbfbf4e8..2a744e6a64ae 100644
--- a/services/core/java/com/android/server/tv/TvInputHal.java
+++ b/services/core/java/com/android/server/tv/TvInputHal.java
@@ -67,6 +67,8 @@ final class TvInputHal implements Handler.Callback {
private static native void nativeClose(long ptr);
private static native int nativeSetTvMessageEnabled(long ptr, int deviceId, int streamId,
int type, boolean enabled);
+ private static native int nativeSetPictureProfile(
+ long ptr, int deviceId, int streamId, long profileHandle);
private final Object mLock = new Object();
private long mPtr = 0;
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index 5eceb6490256..fd4e38e9813d 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -24,10 +24,12 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.ActivityTaskManager.INVALID_WINDOWING_MODE;
import static android.app.FullscreenRequestHandler.REMOTE_CALLBACK_RESULT_KEY;
import static android.app.FullscreenRequestHandler.RESULT_APPROVED;
+import static android.app.FullscreenRequestHandler.RESULT_FAILED_ALREADY_FULLY_EXPANDED;
import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
import static android.app.FullscreenRequestHandler.RESULT_FAILED_NOT_TOP_FOCUSED;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
@@ -1189,17 +1191,25 @@ class ActivityClientController extends IActivityClientController.Stub {
if (requesterActivity.getWindowingMode() == WINDOWING_MODE_PINNED) {
return RESULT_APPROVED;
}
+ final int taskWindowingMode = topFocusedRootTask.getWindowingMode();
// If this is not coming from the currently top-most activity, reject the request.
if (requesterActivity != topFocusedRootTask.getTopMostActivity()) {
return RESULT_FAILED_NOT_TOP_FOCUSED;
}
if (fullscreenRequest == FULLSCREEN_MODE_REQUEST_EXIT) {
- if (topFocusedRootTask.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
+ if (taskWindowingMode != WINDOWING_MODE_FULLSCREEN) {
return RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
}
if (topFocusedRootTask.mMultiWindowRestoreWindowingMode == INVALID_WINDOWING_MODE) {
return RESULT_FAILED_NOT_IN_FULLSCREEN_WITH_HISTORY;
}
+ return RESULT_APPROVED;
+ }
+
+ if (DesktopModeFlags.ENABLE_REQUEST_FULLSCREEN_BUGFIX.isTrue()
+ && (taskWindowingMode == WINDOWING_MODE_FULLSCREEN
+ || taskWindowingMode == WINDOWING_MODE_MULTI_WINDOW)) {
+ return RESULT_FAILED_ALREADY_FULLY_EXPANDED;
}
return RESULT_APPROVED;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 00a437cc31f9..c23dabcd2a48 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -67,7 +67,6 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL;
-import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
@@ -99,7 +98,6 @@ import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ER
import static android.view.flags.Flags.sensitiveContentAppProtection;
import static android.window.WindowProviderService.isWindowProviderService;
-import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_ANIM;
import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_BOOT;
@@ -9241,25 +9239,6 @@ public class WindowManagerService extends IWindowManager.Stub
+ "' because it isn't a trusted overlay");
return inputFeatures & ~INPUT_FEATURE_SENSITIVE_FOR_PRIVACY;
}
-
- // You need OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW permission to be able
- // to set INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS.
- if (overridePowerKeyBehaviorInFocusedWindow()
- && (inputFeatures
- & INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS)
- != 0) {
- final int powerPermissionResult =
- mContext.checkPermission(
- permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW,
- callingPid,
- callingUid);
- if (powerPermissionResult != PackageManager.PERMISSION_GRANTED) {
- throw new IllegalArgumentException(
- "Cannot use INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS from" + windowName
- + " because it doesn't have the"
- + " OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW permission");
- }
- }
return inputFeatures;
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a03b765cae6a..93876f5eeed4 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -169,6 +169,7 @@ import static com.android.server.wm.WindowStateProto.IS_READY_FOR_DISPLAY;
import static com.android.server.wm.WindowStateProto.IS_VISIBLE;
import static com.android.server.wm.WindowStateProto.KEEP_CLEAR_AREAS;
import static com.android.server.wm.WindowStateProto.MERGED_LOCAL_INSETS_SOURCES;
+import static com.android.server.wm.WindowStateProto.PREPARE_SYNC_SEQ_ID;
import static com.android.server.wm.WindowStateProto.REMOVED;
import static com.android.server.wm.WindowStateProto.REMOVE_ON_EXIT;
import static com.android.server.wm.WindowStateProto.REQUESTED_HEIGHT;
@@ -177,6 +178,7 @@ import static com.android.server.wm.WindowStateProto.REQUESTED_WIDTH;
import static com.android.server.wm.WindowStateProto.STACK_ID;
import static com.android.server.wm.WindowStateProto.SURFACE_INSETS;
import static com.android.server.wm.WindowStateProto.SURFACE_POSITION;
+import static com.android.server.wm.WindowStateProto.SYNC_SEQ_ID;
import static com.android.server.wm.WindowStateProto.UNRESTRICTED_KEEP_CLEAR_AREAS;
import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY;
import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER;
@@ -3945,6 +3947,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
dimBounds.dumpDebug(proto, DIM_BOUNDS);
}
}
+ proto.write(SYNC_SEQ_ID, mSyncSeqId);
+ proto.write(PREPARE_SYNC_SEQ_ID, mPrepareSyncSeqId);
proto.end(token);
}
@@ -5507,10 +5511,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
|| mKeyInterceptionInfo.layoutParamsPrivateFlags != mAttrs.privateFlags
|| mKeyInterceptionInfo.layoutParamsType != mAttrs.type
|| mKeyInterceptionInfo.windowTitle != getWindowTag()
- || mKeyInterceptionInfo.windowOwnerUid != getOwningUid()
- || mKeyInterceptionInfo.inputFeaturesFlags != mAttrs.inputFeatures) {
+ || mKeyInterceptionInfo.windowOwnerUid != getOwningUid()) {
mKeyInterceptionInfo = new KeyInterceptionInfo(mAttrs.type, mAttrs.privateFlags,
- getWindowTag().toString(), getOwningUid(), mAttrs.inputFeatures);
+ getWindowTag().toString(), getOwningUid());
}
return mKeyInterceptionInfo;
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index adfabe1e54fd..e49e60632d0e 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -191,7 +191,7 @@ cc_defaults {
"android.hardware.thermal@1.0",
"android.hardware.thermal-V3-ndk",
"android.hardware.tv.input@1.0",
- "android.hardware.tv.input-V2-ndk",
+ "android.hardware.tv.input-V3-ndk",
"android.hardware.vibrator-V3-ndk",
"android.hardware.vr@1.0",
"android.hidl.token@1.0-utils",
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index e29511564cea..ee7c9368f897 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -2926,6 +2926,12 @@ static void nativeReloadDeviceAliases(JNIEnv* env, jobject nativeImplObj) {
InputReaderConfiguration::Change::DEVICE_ALIAS);
}
+static jstring nativeGetSysfsRootPath(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+ NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+ const auto path = im->getInputManager()->getReader().getSysfsRootPath(deviceId);
+ return path.empty() ? nullptr : env->NewStringUTF(path.c_str());
+}
+
static void nativeSysfsNodeChanged(JNIEnv* env, jobject nativeImplObj, jstring path) {
ScopedUtfChars sysfsNodePathChars(env, path);
const std::string sysfsNodePath = sysfsNodePathChars.c_str();
@@ -3388,6 +3394,7 @@ static const JNINativeMethod gInputManagerMethods[] = {
{"getBatteryDevicePath", "(I)Ljava/lang/String;", (void*)nativeGetBatteryDevicePath},
{"reloadKeyboardLayouts", "()V", (void*)nativeReloadKeyboardLayouts},
{"reloadDeviceAliases", "()V", (void*)nativeReloadDeviceAliases},
+ {"getSysfsRootPath", "(I)Ljava/lang/String;", (void*)nativeGetSysfsRootPath},
{"sysfsNodeChanged", "(Ljava/lang/String;)V", (void*)nativeSysfsNodeChanged},
{"dump", "()Ljava/lang/String;", (void*)nativeDump},
{"monitor", "()V", (void*)nativeMonitor},
diff --git a/services/core/jni/com_android_server_tv_TvInputHal.cpp b/services/core/jni/com_android_server_tv_TvInputHal.cpp
index 1e6384031f9a..def95daea92d 100644
--- a/services/core/jni/com_android_server_tv_TvInputHal.cpp
+++ b/services/core/jni/com_android_server_tv_TvInputHal.cpp
@@ -91,6 +91,12 @@ static int nativeSetTvMessageEnabled(JNIEnv* env, jclass clazz, jlong ptr, jint
return tvInputHal->setTvMessageEnabled(deviceId, streamId, type, enabled);
}
+static int nativeSetPictureProfile(JNIEnv* env, jclass clazz, jlong ptr, jint deviceId,
+ jint streamId, jlong profileHandle) {
+ JTvInputHal* tvInputHal = (JTvInputHal*)ptr;
+ return tvInputHal->setPictureProfileId(deviceId, streamId, profileHandle);
+}
+
static void nativeClose(JNIEnv* env, jclass clazz, jlong ptr) {
JTvInputHal* tvInputHal = (JTvInputHal*)ptr;
delete tvInputHal;
@@ -104,6 +110,7 @@ static const JNINativeMethod gTvInputHalMethods[] = {
{"nativeGetStreamConfigs", "(JII)[Landroid/media/tv/TvStreamConfig;",
(void*)nativeGetStreamConfigs},
{"nativeSetTvMessageEnabled", "(JIIIZ)I", (void*)nativeSetTvMessageEnabled},
+ {"nativeSetPictureProfile", "(JIIJ)I", (void*)nativeSetPictureProfile},
{"nativeClose", "(J)V", (void*)nativeClose},
};
diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp
index 505421e81d3d..e4821299eee9 100644
--- a/services/core/jni/tvinput/JTvInputHal.cpp
+++ b/services/core/jni/tvinput/JTvInputHal.cpp
@@ -156,6 +156,15 @@ int JTvInputHal::setTvMessageEnabled(int deviceId, int streamId, int type, bool
return NO_ERROR;
}
+int JTvInputHal::setPictureProfileId(int deviceId, int streamId, long profileHandle) {
+ ::ndk::ScopedAStatus status = mTvInput->setPictureProfileId(deviceId, streamId, profileHandle);
+ if (!status.isOk()) {
+ ALOGE("Error in setPictureProfileId. device id:%d stream id:%d", deviceId, streamId);
+ return status.getStatus();
+ }
+ return NO_ERROR;
+}
+
const std::vector<AidlTvStreamConfig> JTvInputHal::getStreamConfigs(int deviceId) {
std::vector<AidlTvStreamConfig> list;
::ndk::ScopedAStatus status = mTvInput->getStreamConfigurations(deviceId, &list);
@@ -551,6 +560,16 @@ JTvInputHal::ITvInputWrapper::ITvInputWrapper(std::shared_ptr<AidlITvInput>& aid
}
}
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::setPictureProfileId(int32_t deviceId,
+ int32_t streamId,
+ long profileHandle) {
+ if (mIsHidl) {
+ return ::ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ } else {
+ return mAidlTvInput->setPictureProfileId(deviceId, streamId, profileHandle);
+ }
+}
+
::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::getTvMessageQueueDesc(
MQDescriptor<int8_t, SynchronizedReadWrite>* out_queue, int32_t in_deviceId,
int32_t in_streamId) {
diff --git a/services/core/jni/tvinput/JTvInputHal.h b/services/core/jni/tvinput/JTvInputHal.h
index 2ef94ac4a3b0..4481f1d37c2b 100644
--- a/services/core/jni/tvinput/JTvInputHal.h
+++ b/services/core/jni/tvinput/JTvInputHal.h
@@ -85,6 +85,7 @@ public:
int addOrUpdateStream(int deviceId, int streamId, const sp<Surface>& surface);
int setTvMessageEnabled(int deviceId, int streamId, int type, bool enabled);
+ int setPictureProfileId(int deviceId, int streamId, long profileHandle);
int removeStream(int deviceId, int streamId);
const std::vector<AidlTvStreamConfig> getStreamConfigs(int deviceId);
@@ -208,6 +209,7 @@ private:
::ndk::ScopedAStatus closeStream(int32_t in_deviceId, int32_t in_streamId);
::ndk::ScopedAStatus setTvMessageEnabled(int32_t deviceId, int32_t streamId,
TvMessageEventType in_type, bool enabled);
+ ::ndk::ScopedAStatus setPictureProfileId(int deviceId, int streamId, long profileHandle);
::ndk::ScopedAStatus getTvMessageQueueDesc(
MQDescriptor<int8_t, SynchronizedReadWrite>* out_queue, int32_t in_deviceId,
int32_t in_streamId);
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index eeac70afcffb..f3ab0e33d026 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -1755,13 +1755,6 @@ class AppIdPermissionPolicy : SchemePolicy() {
}
val appIdPermissionFlags = newState.mutateUserState(userId)!!.mutateAppIdPermissionFlags()
val permissionFlags = appIdPermissionFlags.mutateOrPut(appId) { MutableIndexedMap() }
- // for debugging possible races TODO(b/401768134)
- oldState.userStates[userId]?.appIdPermissionFlags[appId]?.map?.let {
- if (permissionFlags.map === it) {
- throw IllegalStateException("Unexpected sharing between old/new state")
- }
- }
-
permissionFlags.putWithDefault(permissionName, newFlags, 0)
if (permissionFlags.isEmpty()) {
appIdPermissionFlags -= appId
diff --git a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
index cc0d5e4710d2..73e5f8232faf 100644
--- a/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/GestureLauncherServiceTest.java
@@ -1787,46 +1787,6 @@ public class GestureLauncherServiceTest {
}
/**
- * If processPowerKeyDown is called instead of interceptPowerKeyDown (meaning the double tap
- * gesture isn't performed), the emergency gesture is still launched.
- */
- @Test
- public void testProcessPowerKeyDown_fiveInboundPresses_emergencyGestureLaunches() {
- enableCameraGesture();
- enableEmergencyGesture();
-
- // First event
- long eventTime = INITIAL_EVENT_TIME_MILLIS;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, false, false);
-
- //Second event; call processPowerKeyDown without calling interceptPowerKeyDown
- final long interval = POWER_DOUBLE_TAP_MAX_TIME_MS - 1;
- eventTime += interval;
- KeyEvent keyEvent =
- new KeyEvent(
- IGNORED_DOWN_TIME, eventTime, IGNORED_ACTION, IGNORED_CODE, IGNORED_REPEAT);
- mGestureLauncherService.processPowerKeyDown(keyEvent);
-
- verify(mMetricsLogger, never())
- .action(eq(MetricsEvent.ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE), anyInt());
- verify(mUiEventLogger, never()).log(any());
-
- // Presses 3 and 4 should not trigger any gesture
- for (int i = 0; i < 2; i++) {
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, false);
- }
-
- // Fifth button press should still trigger the emergency flow
- eventTime += interval;
- sendPowerKeyDownToGestureLauncherServiceAndAssertValues(eventTime, true, true);
-
- verify(mUiEventLogger, times(1))
- .log(GestureLauncherService.GestureLauncherEvent.GESTURE_EMERGENCY_TAP_POWER);
- verify(mStatusBarManagerInternal).onEmergencyActionLaunchGestureDetected();
- }
-
- /**
* Helper method to trigger emergency gesture by pressing button for 5 times.
*
* @return last event time.
diff --git a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
index 53e82bad818d..8d717bc19e72 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PowerKeyGestureTests.java
@@ -18,16 +18,12 @@ package com.android.server.policy;
import static android.view.KeyEvent.KEYCODE_POWER;
import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
-import static com.android.hardware.input.Flags.FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_ASSISTANT;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GLOBAL_ACTIONS;
import static com.android.server.policy.PhoneWindowManager.POWER_MULTI_PRESS_TIMEOUT_MILLIS;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_DREAM_OR_SLEEP;
import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_POWER_GO_TO_SLEEP;
-import static org.junit.Assert.assertEquals;
-
-import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.view.Display;
@@ -153,143 +149,4 @@ public class PowerKeyGestureTests extends ShortcutKeyTestBase {
sendKey(KEYCODE_POWER);
mPhoneWindowManager.assertNoPowerSleep();
}
-
-
- /**
- * Double press of power when the window handles the power key events. The
- * system double power gesture launch should not be performed.
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerDoublePress_windowHasOverridePermissionAndKeysHandled() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> true);
-
- sendKey(KEYCODE_POWER);
- sendKey(KEYCODE_POWER);
-
- mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
-
- mPhoneWindowManager.assertNoDoublePowerLaunch();
- }
-
- /**
- * Double press of power when the window doesn't handle the power key events.
- * The system default gesture launch should be performed and the app should receive both events.
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerDoublePress_windowHasOverridePermissionAndKeysUnHandled() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> false);
-
- sendKey(KEYCODE_POWER);
- sendKey(KEYCODE_POWER);
-
- mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
- mPhoneWindowManager.assertDoublePowerLaunch();
- assertEquals(getDownKeysDispatched(), 2);
- assertEquals(getUpKeysDispatched(), 2);
- }
-
- /**
- * Triple press of power when the window handles the power key double press gesture.
- * The system default gesture launch should not be performed, and the app only receives the
- * first two presses.
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerTriplePress_windowHasOverridePermissionAndKeysHandled() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> true);
-
- sendKey(KEYCODE_POWER);
- sendKey(KEYCODE_POWER);
- sendKey(KEYCODE_POWER);
-
- mPhoneWindowManager.assertDidNotLockAfterAppTransitionFinished();
- mPhoneWindowManager.assertNoDoublePowerLaunch();
- assertEquals(getDownKeysDispatched(), 2);
- assertEquals(getUpKeysDispatched(), 2);
- }
-
- /**
- * Tests a single press, followed by a double press when the window can handle the power key.
- * The app should receive all 3 events.
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerTriplePressWithDelay_windowHasOverridePermissionAndKeysHandled() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> true);
-
- sendKey(KEYCODE_POWER);
- mPhoneWindowManager.moveTimeForward(POWER_MULTI_PRESS_TIMEOUT_MILLIS);
- sendKey(KEYCODE_POWER);
- sendKey(KEYCODE_POWER);
-
- mPhoneWindowManager.assertNoDoublePowerLaunch();
- assertEquals(getDownKeysDispatched(), 3);
- assertEquals(getUpKeysDispatched(), 3);
- }
-
- /**
- * Tests single press when window doesn't handle the power key. Phone should go to sleep.
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerSinglePress_windowHasOverridePermissionAndKeyUnhandledByApp() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> false);
- mPhoneWindowManager.overrideShortPressOnPower(SHORT_PRESS_POWER_GO_TO_SLEEP);
-
- sendKey(KEYCODE_POWER);
-
- mPhoneWindowManager.assertPowerSleep();
- }
-
- /**
- * Tests single press when the window handles the power key. Phone should go to sleep after a
- * delay of {POWER_MULTI_PRESS_TIMEOUT_MILLIS}
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerSinglePress_windowHasOverridePermissionAndKeyHandledByApp() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> true);
- mPhoneWindowManager.overrideDisplayState(Display.STATE_ON);
- mPhoneWindowManager.overrideShortPressOnPower(SHORT_PRESS_POWER_GO_TO_SLEEP);
-
- sendKey(KEYCODE_POWER);
-
- mPhoneWindowManager.moveTimeForward(POWER_MULTI_PRESS_TIMEOUT_MILLIS);
-
- mPhoneWindowManager.assertPowerSleep();
- }
-
-
- /**
- * Tests 5x press when the window handles the power key. Emergency gesture should still be
- * launched.
- */
- @Test
- @EnableFlags(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testPowerFiveTimesPress_windowHasOverridePermissionAndKeyHandledByApp() {
- mPhoneWindowManager.overrideCanWindowOverridePowerKey(true);
- setDispatchedKeyHandler(keyEvent -> true);
- mPhoneWindowManager.overrideDisplayState(Display.STATE_ON);
- mPhoneWindowManager.overrideShortPressOnPower(SHORT_PRESS_POWER_GO_TO_SLEEP);
-
- int minEmergencyGestureDurationMillis = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_defaultMinEmergencyGestureTapDurationMillis);
- int durationMillis = minEmergencyGestureDurationMillis / 4;
- for (int i = 0; i < 5; ++i) {
- sendKey(KEYCODE_POWER);
- mPhoneWindowManager.moveTimeForward(durationMillis);
- }
-
- mPhoneWindowManager.assertEmergencyLaunch();
- assertEquals(getDownKeysDispatched(), 2);
- assertEquals(getUpKeysDispatched(), 2);
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 7059c41898f3..2097d15658a6 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -37,7 +37,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.hardware.input.Flags.overridePowerKeyBehaviorInFocusedWindow;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_ASSISTANT;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GLOBAL_ACTIONS;
import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_POWER_GO_TO_VOICE_ASSIST;
@@ -210,8 +209,6 @@ class TestPhoneWindowManager {
private int mKeyEventPolicyFlags = FLAG_INTERACTIVE;
- private int mProcessPowerKeyDownCount = 0;
-
private class TestTalkbackShortcutController extends TalkbackShortcutController {
TestTalkbackShortcutController(Context context) {
super(context);
@@ -424,7 +421,7 @@ class TestPhoneWindowManager {
doNothing().when(mContext).startActivityAsUser(any(), any());
doNothing().when(mContext).startActivityAsUser(any(), any(), any());
- KeyInterceptionInfo interceptionInfo = new KeyInterceptionInfo(0, 0, null, 0, 0);
+ KeyInterceptionInfo interceptionInfo = new KeyInterceptionInfo(0, 0, null, 0);
doReturn(interceptionInfo)
.when(mWindowManagerInternal).getKeyInterceptionInfoFromToken(any());
@@ -442,9 +439,6 @@ class TestPhoneWindowManager {
eq(TEST_BROWSER_ROLE_PACKAGE_NAME));
doReturn(mSmsIntent).when(mPackageManager).getLaunchIntentForPackage(
eq(TEST_SMS_ROLE_PACKAGE_NAME));
- mProcessPowerKeyDownCount = 0;
- captureProcessPowerKeyDownCount();
-
Mockito.reset(mContext);
}
@@ -715,12 +709,6 @@ class TestPhoneWindowManager {
.when(mButtonOverridePermissionChecker).canAppOverrideSystemKey(any(), anyInt());
}
- void overrideCanWindowOverridePowerKey(boolean granted) {
- doReturn(granted)
- .when(mButtonOverridePermissionChecker).canWindowOverridePowerKey(any(), anyInt(),
- anyInt());
- }
-
void overrideKeyEventPolicyFlags(int flags) {
mKeyEventPolicyFlags = flags;
}
@@ -800,10 +788,6 @@ class TestPhoneWindowManager {
verify(mGestureLauncherService, atMost(4))
.interceptPowerKeyDown(any(), anyBoolean(), valueCaptor.capture());
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- assertTrue(mProcessPowerKeyDownCount >= 2 && mProcessPowerKeyDownCount <= 4);
- }
-
List<Boolean> capturedValues = valueCaptor.getAllValues().stream()
.map(mutableBoolean -> mutableBoolean.value)
.toList();
@@ -832,10 +816,6 @@ class TestPhoneWindowManager {
verify(mGestureLauncherService, atLeast(1))
.interceptPowerKeyDown(any(), anyBoolean(), valueCaptor.capture());
- if (overridePowerKeyBehaviorInFocusedWindow()) {
- assertEquals(mProcessPowerKeyDownCount, 5);
- }
-
List<Boolean> capturedValues = valueCaptor.getAllValues().stream()
.map(mutableBoolean -> mutableBoolean.value)
.toList();
@@ -1063,12 +1043,4 @@ class TestPhoneWindowManager {
verify(mContext, never()).startActivityAsUser(any(), any(), any());
verify(mContext, never()).startActivityAsUser(any(), any());
}
-
- private void captureProcessPowerKeyDownCount() {
- doAnswer((Answer<Void>) invocation -> {
- invocation.callRealMethod();
- mProcessPowerKeyDownCount++;
- return null;
- }).when(mGestureLauncherService).processPowerKeyDown(any());
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 5427dc22e700..795273d47230 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -29,7 +29,6 @@ import static android.view.Display.FLAG_OWN_FOCUS;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
-import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SENSITIVE_FOR_PRIVACY;
import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
@@ -49,7 +48,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.hardware.input.Flags.FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
@@ -1154,53 +1152,6 @@ public class WindowManagerServiceTests extends WindowTestsBase {
}
@Test
- @RequiresFlagsEnabled(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testUpdateInputChannel_sanitizeWithoutPermission_ThrowsError() {
- final Session session = mock(Session.class);
- final int callingUid = Process.FIRST_APPLICATION_UID;
- final int callingPid = 1234;
- final SurfaceControl surfaceControl = mock(SurfaceControl.class);
- final IBinder window = new Binder();
- final InputTransferToken inputTransferToken = mock(InputTransferToken.class);
-
-
- final InputChannel inputChannel = new InputChannel();
-
- assertThrows(IllegalArgumentException.class, () ->
- mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY,
- surfaceControl, window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE,
- 0 /* privateFlags */,
- INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS,
- TYPE_APPLICATION, null /* windowToken */, inputTransferToken,
- "TestInputChannel", inputChannel));
- }
-
-
- @Test
- @RequiresFlagsEnabled(FLAG_OVERRIDE_POWER_KEY_BEHAVIOR_IN_FOCUSED_WINDOW)
- public void testUpdateInputChannel_sanitizeWithPermission_doesNotThrowError() {
- final Session session = mock(Session.class);
- final int callingUid = Process.FIRST_APPLICATION_UID;
- final int callingPid = 1234;
- final SurfaceControl surfaceControl = mock(SurfaceControl.class);
- final IBinder window = new Binder();
- final InputTransferToken inputTransferToken = mock(InputTransferToken.class);
-
- doReturn(PackageManager.PERMISSION_GRANTED).when(mWm.mContext).checkPermission(
- android.Manifest.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW,
- callingPid,
- callingUid);
-
- final InputChannel inputChannel = new InputChannel();
-
- mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, surfaceControl,
- window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, 0 /* privateFlags */,
- INPUT_FEATURE_RECEIVE_POWER_KEY_DOUBLE_PRESS,
- TYPE_APPLICATION, null /* windowToken */, inputTransferToken, "TestInputChannel",
- inputChannel);
- }
-
- @Test
public void testUpdateInputChannel_allowSpyWindowForInputMonitorPermission() {
final Session session = mock(Session.class);
final int callingUid = Process.SYSTEM_UID;
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index ebe00782319a..68216b2dbd8a 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -912,6 +912,16 @@ public abstract class Connection extends Conferenceable {
public static final String EVENT_CALL_HOLD_FAILED = "android.telecom.event.CALL_HOLD_FAILED";
/**
+ * Connection event used to inform Telecom when a resume operation on a call has failed.
+ * <p>
+ * Sent via {@link #sendConnectionEvent(String, Bundle)}. The {@link Bundle} parameter is
+ * expected to be null when this connection event is used.
+ */
+ @FlaggedApi(Flags.FLAG_CALL_SEQUENCING_CALL_RESUME_FAILED)
+ public static final String EVENT_CALL_RESUME_FAILED =
+ "android.telecom.event.CALL_RESUME_FAILED";
+
+ /**
* Connection event used to inform Telecom when a switch operation on a call has failed.
* <p>
* Sent via {@link #sendConnectionEvent(String, Bundle)}. The {@link Bundle} parameter is
diff --git a/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
index a08b650b4c2f..9c176cfe45e5 100644
--- a/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
+++ b/tests/AppJankTest/src/android/app/jank/tests/IntegrationTests.java
@@ -60,6 +60,7 @@ import java.util.HashMap;
@RunWith(AndroidJUnit4.class)
public class IntegrationTests {
public static final int WAIT_FOR_TIMEOUT_MS = 5000;
+ public static final int WAIT_FOR_PENDING_JANKSTATS_MS = 1000;
@Rule
public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -116,18 +117,8 @@ public class IntegrationTests {
editText.reportAppJankStats(JankUtils.getAppJankStats());
- // reportAppJankStats performs the work on a background thread, check periodically to see
- // if the work is complete.
- for (int i = 0; i < 10; i++) {
- try {
- Thread.sleep(100);
- if (jankTracker.getPendingJankStats().size() > 0) {
- break;
- }
- } catch (InterruptedException exception) {
- //do nothing and continue
- }
- }
+ // wait until pending results are available.
+ JankUtils.waitForResults(jankTracker, WAIT_FOR_PENDING_JANKSTATS_MS);
pendingStats = jankTracker.getPendingJankStats();
@@ -247,18 +238,8 @@ public class IntegrationTests {
int mismatchedAppUID = 25;
editText.reportAppJankStats(JankUtils.getAppJankStats(mismatchedAppUID));
- // reportAppJankStats performs the work on a background thread, check periodically to see
- // if the work is complete.
- for (int i = 0; i < 10; i++) {
- try {
- Thread.sleep(100);
- if (jankTracker.getPendingJankStats().size() > 0) {
- break;
- }
- } catch (InterruptedException exception) {
- //do nothing and continue
- }
- }
+ // wait until pending results should be available.
+ JankUtils.waitForResults(jankTracker, WAIT_FOR_PENDING_JANKSTATS_MS);
pendingStats = jankTracker.getPendingJankStats();
diff --git a/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java b/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java
index 7067b873d4b7..302cad11bbb9 100644
--- a/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java
+++ b/tests/AppJankTest/src/android/app/jank/tests/JankUtils.java
@@ -17,6 +17,7 @@
package android.app.jank.tests;
import android.app.jank.AppJankStats;
+import android.app.jank.JankTracker;
import android.app.jank.RelativeFrameTimeHistogram;
import android.os.Process;
@@ -56,4 +57,26 @@ public class JankUtils {
overrunHistogram.addRelativeFrameTimeMillis(25);
return overrunHistogram;
}
+
+ /**
+ * When JankStats are reported they are processed on a background thread. This method checks
+ * every 100 ms up to the maxWaitTime to see if the pending stat count is greater than zero.
+ * If the pending stat count is greater than zero it will return or keep trying until
+ * maxWaitTime has elapsed.
+ */
+ public static void waitForResults(JankTracker jankTracker, int maxWaitTimeMs) {
+ int currentWaitTimeMs = 0;
+ int threadSleepTimeMs = 100;
+ while (currentWaitTimeMs < maxWaitTimeMs) {
+ try {
+ Thread.sleep(threadSleepTimeMs);
+ if (!jankTracker.getPendingJankStats().isEmpty()) {
+ return;
+ }
+ currentWaitTimeMs += threadSleepTimeMs;
+ } catch (InterruptedException exception) {
+ // do nothing and continue.
+ }
+ }
+ }
}
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 4737d19acde1..1858b1da916b 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -714,18 +714,16 @@ class InputManagerServiceTests {
)
}
- val info =
- KeyInterceptionInfo(
- /* type = */ 0,
- if (hasPrivateFlag) {
- WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS
- } else {
- 0
- },
- "title",
- /* uid = */ 0,
- /* inputFeatureFlags = */ 0,
- )
+ val info = KeyInterceptionInfo(
+ /* type = */0,
+ if (hasPrivateFlag) {
+ WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS
+ } else {
+ 0
+ },
+ "title",
+ /* uid = */0
+ )
whenever(windowManagerInternal.getKeyInterceptionInfoFromToken(any())).thenReturn(info)
}
}