summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
Diffstat (limited to 'packages')
-rw-r--r--packages/CompanionDeviceManager/res/values/strings.xml13
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java6
-rw-r--r--packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java12
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_normal.xml4
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_filled_button_content_high.xml22
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_filled_button_content_low.xml22
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_filled_button_content_medium.xml22
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_filled_button_content_normal.xml22
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_outline_button_content.xml22
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_outline_button_stroke_normal.xml22
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/values-v36/styles_expressive.xml1
-rw-r--r--packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java74
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_button_background_filled.xml21
-rw-r--r--packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_button_background_outline.xml21
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v36/styles_expressive.xml35
-rw-r--r--packages/SettingsLib/SettingsTheme/res/values-v36/themes_expressive.xml13
-rw-r--r--packages/SettingsLib/res/xml/timezones.xml1
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt8
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java240
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java3
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java39
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java15
-rw-r--r--packages/Shell/AndroidManifest.xml2
-rw-r--r--packages/SystemUI/aconfig/systemui.aconfig38
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt3
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt23
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt74
-rw-r--r--packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt77
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt16
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt30
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java3
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt11
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java18
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java54
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt14
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt708
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationsInteractorTest.kt147
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java29
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt26
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java35
-rw-r--r--packages/SystemUI/res/drawable/notification_menu_button_background.xml21
-rw-r--r--packages/SystemUI/res/drawable/unpin_icon.xml12
-rw-r--r--packages/SystemUI/res/layout/battery_percentage_view.xml2
-rw-r--r--packages/SystemUI/res/layout/keyguard_status_bar.xml2
-rw-r--r--packages/SystemUI/res/layout/notification_conversation_info.xml10
-rw-r--r--packages/SystemUI/res/layout/promoted_menu_item.xml54
-rw-r--r--packages/SystemUI/res/layout/promoted_permission_guts.xml2
-rw-r--r--packages/SystemUI/res/layout/status_bar.xml2
-rw-r--r--packages/SystemUI/res/layout/system_icons.xml2
-rw-r--r--packages/SystemUI/res/values/dimens.xml7
-rw-r--r--packages/SystemUI/res/values/strings.xml7
-rw-r--r--packages/SystemUI/res/values/styles.xml17
-rw-r--r--packages/SystemUI/src/com/android/keyguard/ClockEventController.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt19
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt39
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java117
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/OWNERS4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java110
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.kt64
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryWithDismissStats.kt7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java66
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt28
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java55
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationTemplateViewWrapper.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/SensitiveNotificationProtectionInteractor.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/annotations/DeprecatedSysuiVisibleForTesting.kt28
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java12
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt2
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractorKosmos.kt4
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/SensitiveNotificationProtectionInteractorKosmos.kt25
106 files changed, 2174 insertions, 927 deletions
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 60f209b47482..574671376e2e 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -106,16 +106,13 @@
<!-- Description of the helper dialog for NEARBY_DEVICE_STREAMING profile. [CHAR LIMIT=NONE] -->
<string name="helper_summary_nearby_device_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> is requesting permission on behalf of <xliff:g id="device_name" example="Chromebook">%2$s</xliff:g> to stream apps from your <xliff:g id="device_type" example="phone">%3$s</xliff:g></string>
- <!-- ================= DEVICE_PROFILE_SENSOR_DEVICE_STREAMING ================= -->
+ <!-- ================= DEVICE_PROFILE_VIRTUAL_DEVICE ================= -->
- <!-- Confirmation for associating an application with a companion device of SENSOR_DEVICE_STREAMING profile (type) [CHAR LIMIT=NONE] -->
- <string name="title_sensor_device_streaming">Allow &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to stream audio and system features between your <xliff:g id="device_type" example="phone">%2$s</xliff:g> and &lt;strong&gt;<xliff:g id="device_name" example="Chromebook">%3$s</xliff:g>&lt;/strong&gt;?</string>
+ <!-- Confirmation for associating an application with a companion device of VIRTUAL_DEVICE profile (type) [CHAR LIMIT=NONE] -->
+ <string name="title_virtual_device">Allow &lt;strong&gt;<xliff:g id="app_name" example="Exo">%1$s</xliff:g>&lt;/strong&gt; to stream audio and system features between your <xliff:g id="device_type" example="phone">%3$s</xliff:g> and &lt;strong&gt;<xliff:g id="device_name" example="Chromebook">%2$s</xliff:g>&lt;/strong&gt;?</string>
- <!-- Summary for associating an application with a companion device of SENSOR_DEVICE_STREAMING profile [CHAR LIMIT=NONE] -->
- <string name="summary_sensor_device_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will have access to anything that’s played on your <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g>.&lt;br/>&lt;br/><xliff:g id="app_name" example="Exo">%1$s</xliff:g> will be able to stream audio to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
-
- <!-- Description of the helper dialog for SENSOR_DEVICE_STREAMING profile. [CHAR LIMIT=NONE] -->
- <string name="helper_summary_sensor_device_streaming"><xliff:g id="app_name" example="Exo">%1$s</xliff:g> is requesting permission on behalf of <xliff:g id="device_name" example="Chromebook">%2$s</xliff:g> to stream audio and system features between your devices.</string>
+ <!-- Summary for associating an application with a companion device of VIRTUAL_DEVICE profile [CHAR LIMIT=NONE] -->
+ <string name="summary_virtual_device"><xliff:g id="app_name" example="Exo">%2$s</xliff:g> will have access to anything that’s played on <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g>.&lt;br/>&lt;br/><xliff:g id="app_name" example="Exo">%2$s</xliff:g> will be able to stream audio to <xliff:g id="device_name" example="Chromebook">%3$s</xliff:g> until you remove access to this permission.</string>
<!-- ================= null profile ================= -->
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
index 518757dd0d5c..c07e572eb649 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionAssociationActivity.java
@@ -667,7 +667,8 @@ public class CompanionAssociationActivity extends FragmentActivity implements
final int summaryResourceId = PROFILE_SUMMARIES.get(deviceProfile);
final String remoteDeviceName = mSelectedDevice.getDisplayName();
final Spanned title = getHtmlFromResources(
- this, PROFILE_TITLES.get(deviceProfile), mAppLabel, remoteDeviceName);
+ this, PROFILE_TITLES.get(deviceProfile), mAppLabel, remoteDeviceName,
+ getString(R.string.device_type));
final Spanned summary;
if (deviceProfile == null && mRequest.isSingleDevice()) {
@@ -675,7 +676,8 @@ public class CompanionAssociationActivity extends FragmentActivity implements
mConstraintList.setVisibility(View.GONE);
} else {
summary = getHtmlFromResources(
- this, summaryResourceId, getString(R.string.device_type));
+ this, summaryResourceId, getString(R.string.device_type), mAppLabel,
+ remoteDeviceName);
setupPermissionList(deviceProfile);
}
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
index f756a6235c14..f6e680207530 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
@@ -21,7 +21,7 @@ import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PRO
import static android.companion.AssociationRequest.DEVICE_PROFILE_COMPUTER;
import static android.companion.AssociationRequest.DEVICE_PROFILE_GLASSES;
import static android.companion.AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_SENSOR_DEVICE_STREAMING;
+import static android.companion.AssociationRequest.DEVICE_PROFILE_VIRTUAL_DEVICE;
import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
import static android.companion.AssociationRequest.DEVICE_PROFILE_WEARABLE_SENSING;
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
@@ -118,7 +118,7 @@ final class CompanionDeviceResources {
map.put(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, R.string.title_automotive_projection);
map.put(DEVICE_PROFILE_COMPUTER, R.string.title_computer);
map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, R.string.title_nearby_device_streaming);
- map.put(DEVICE_PROFILE_SENSOR_DEVICE_STREAMING, R.string.title_sensor_device_streaming);
+ map.put(DEVICE_PROFILE_VIRTUAL_DEVICE, R.string.title_virtual_device);
map.put(DEVICE_PROFILE_WATCH, R.string.confirmation_title);
map.put(DEVICE_PROFILE_GLASSES, R.string.confirmation_title_glasses);
map.put(null, R.string.confirmation_title);
@@ -133,7 +133,7 @@ final class CompanionDeviceResources {
map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses);
map.put(DEVICE_PROFILE_APP_STREAMING, R.string.summary_app_streaming);
map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, R.string.summary_nearby_device_streaming);
- map.put(DEVICE_PROFILE_SENSOR_DEVICE_STREAMING, R.string.summary_sensor_device_streaming);
+ map.put(DEVICE_PROFILE_VIRTUAL_DEVICE, R.string.summary_virtual_device);
map.put(null, R.string.summary_generic);
PROFILE_SUMMARIES = unmodifiableMap(map);
@@ -145,8 +145,6 @@ final class CompanionDeviceResources {
map.put(DEVICE_PROFILE_APP_STREAMING, R.string.helper_summary_app_streaming);
map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
R.string.helper_summary_nearby_device_streaming);
- map.put(DEVICE_PROFILE_SENSOR_DEVICE_STREAMING,
- R.string.helper_summary_sensor_device_streaming);
map.put(DEVICE_PROFILE_COMPUTER, R.string.helper_summary_computer);
PROFILE_HELPER_SUMMARIES = unmodifiableMap(map);
@@ -178,6 +176,7 @@ final class CompanionDeviceResources {
final Map<String, Integer> map = new ArrayMap<>();
map.put(DEVICE_PROFILE_WATCH, R.string.profile_name_watch);
map.put(DEVICE_PROFILE_GLASSES, R.string.profile_name_glasses);
+ map.put(DEVICE_PROFILE_VIRTUAL_DEVICE, R.string.profile_name_generic);
map.put(null, R.string.profile_name_generic);
PROFILE_NAMES = unmodifiableMap(map);
@@ -188,6 +187,7 @@ final class CompanionDeviceResources {
final Map<String, Integer> map = new ArrayMap<>();
map.put(DEVICE_PROFILE_WATCH, R.drawable.ic_watch);
map.put(DEVICE_PROFILE_GLASSES, R.drawable.ic_glasses);
+ map.put(DEVICE_PROFILE_VIRTUAL_DEVICE, R.drawable.ic_device_other);
map.put(null, R.drawable.ic_device_other);
PROFILE_ICONS = unmodifiableMap(map);
@@ -198,6 +198,7 @@ final class CompanionDeviceResources {
final Set<String> set = new ArraySet<>();
set.add(DEVICE_PROFILE_WATCH);
set.add(DEVICE_PROFILE_GLASSES);
+ set.add(DEVICE_PROFILE_VIRTUAL_DEVICE);
set.add(null);
SUPPORTED_PROFILES = unmodifiableSet(set);
@@ -210,7 +211,6 @@ final class CompanionDeviceResources {
set.add(DEVICE_PROFILE_COMPUTER);
set.add(DEVICE_PROFILE_AUTOMOTIVE_PROJECTION);
set.add(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING);
- set.add(DEVICE_PROFILE_SENSOR_DEVICE_STREAMING);
set.add(DEVICE_PROFILE_WEARABLE_SENSING);
set.add(null);
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_normal.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_normal.xml
index 8037a8bb75be..8a234fa6ca9e 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_normal.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_button_background_normal.xml
@@ -17,8 +17,8 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false"
- android:alpha="@dimen/material_emphasis_disabled_background" android:color="?attr/colorOnSurface"/>
+ android:alpha="@dimen/material_emphasis_disabled_background" android:color="@color/settingslib_materialColorPrimary"/>
<item android:state_checked="true" android:color="?attr/colorContainerChecked"/>
<item android:state_checkable="true" android:color="?attr/colorContainerUnchecked"/>
- <item android:color="?attr/colorContainer" />
+ <item android:color="@color/settingslib_materialColorPrimary" />
</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_filled_button_content_high.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_filled_button_content_high.xml
new file mode 100644
index 000000000000..43b236938956
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_filled_button_content_high.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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled" android:color="@color/settingslib_colorContentLevel_high"/>
+ <item android:color="@color/settingslib_colorContentLevel_high" />
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_filled_button_content_low.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_filled_button_content_low.xml
new file mode 100644
index 000000000000..b7a9d7c5175b
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_filled_button_content_low.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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled" android:color="@color/settingslib_colorContentLevel_low"/>
+ <item android:color="@color/settingslib_colorContentLevel_low" />
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_filled_button_content_medium.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_filled_button_content_medium.xml
new file mode 100644
index 000000000000..8e41cb03f4d1
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_filled_button_content_medium.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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled" android:color="@color/settingslib_colorContentLevel_medium"/>
+ <item android:color="@color/settingslib_colorContentLevel_medium" />
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_filled_button_content_normal.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_filled_button_content_normal.xml
new file mode 100644
index 000000000000..1dd5cdecfffc
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_filled_button_content_normal.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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled" android:color="@color/settingslib_materialColorOnPrimary"/>
+ <item android:color="@color/settingslib_materialColorOnPrimary" />
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_outline_button_content.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_outline_button_content.xml
new file mode 100644
index 000000000000..3a06fb38d5d8
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_outline_button_content.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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled" android:color="@color/settingslib_materialColorOnSurface"/>
+ <item android:color="@color/settingslib_materialColorOnSurface" />
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_outline_button_stroke_normal.xml b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_outline_button_stroke_normal.xml
new file mode 100644
index 000000000000..8d0b65712d35
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/color/settingslib_banner_outline_button_stroke_normal.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.
+ -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false"
+ android:alpha="@dimen/material_emphasis_disabled_background" android:color="@color/settingslib_materialColorOutline"/>
+ <item android:color="@color/settingslib_materialColorOutline" />
+</selector> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-v36/styles_expressive.xml b/packages/SettingsLib/BannerMessagePreference/res/values-v36/styles_expressive.xml
index 09e07ccef683..cd9faecc49c4 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values-v36/styles_expressive.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-v36/styles_expressive.xml
@@ -64,7 +64,6 @@
<style name="Banner.PositiveButton.SettingsLib.Expressive"
parent="@style/SettingsLibButtonStyle.Expressive.Filled.Extra">
- <item name="android:textColor">?android:attr/textColorPrimaryInverse</item>
<item name="materialSizeOverlay">@style/SizeOverlay.Material3Expressive.Button.Small</item>
</style>
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
index c90a76a39510..dbd0f6424ff8 100644
--- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
@@ -58,35 +58,42 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
HIGH(0,
R.color.banner_background_attention_high,
R.color.banner_accent_attention_high,
- R.color.settingslib_banner_button_background_high),
+ R.color.settingslib_banner_button_background_high,
+ R.color.settingslib_banner_filled_button_content_high),
MEDIUM(1,
R.color.banner_background_attention_medium,
R.color.banner_accent_attention_medium,
- R.color.settingslib_banner_button_background_medium),
+ R.color.settingslib_banner_button_background_medium,
+ R.color.settingslib_banner_filled_button_content_medium),
LOW(2,
R.color.banner_background_attention_low,
R.color.banner_accent_attention_low,
- R.color.settingslib_banner_button_background_low),
+ R.color.settingslib_banner_button_background_low,
+ R.color.settingslib_banner_filled_button_content_low),
NORMAL(3,
R.color.banner_background_attention_normal,
R.color.banner_accent_attention_normal,
- R.color.settingslib_banner_button_background_normal);
+ R.color.settingslib_banner_button_background_normal,
+ R.color.settingslib_banner_filled_button_content_normal);
// Corresponds to the enum value of R.attr.attentionLevel
private final int mAttrValue;
@ColorRes private final int mBackgroundColorResId;
@ColorRes private final int mAccentColorResId;
@ColorRes private final int mButtonBackgroundColorResId;
+ @ColorRes private final int mButtonContentColorResId;
AttentionLevel(
int attrValue,
@ColorRes int backgroundColorResId,
@ColorRes int accentColorResId,
- @ColorRes int buttonBackgroundColorResId) {
+ @ColorRes int buttonBackgroundColorResId,
+ @ColorRes int buttonContentColorResId) {
mAttrValue = attrValue;
mBackgroundColorResId = backgroundColorResId;
mAccentColorResId = accentColorResId;
mButtonBackgroundColorResId = buttonBackgroundColorResId;
+ mButtonContentColorResId = buttonContentColorResId;
}
static AttentionLevel fromAttr(int attrValue) {
@@ -109,6 +116,10 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
public @ColorRes int getButtonBackgroundColorResId() {
return mButtonBackgroundColorResId;
}
+
+ public @ColorRes int getButtonContentColorResId() {
+ return mButtonContentColorResId;
+ }
}
private static final String TAG = "BannerPreference";
@@ -181,6 +192,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final Context context = getContext();
+ final Resources resources = context.getResources();
final TextView titleView = (TextView) holder.findViewById(R.id.banner_title);
CharSequence title = getTitle();
@@ -200,7 +212,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
final Resources.Theme theme = context.getTheme();
@ColorInt final int accentColor =
- context.getResources().getColor(mAttentionLevel.getAccentColorResId(), theme);
+ resources.getColor(mAttentionLevel.getAccentColorResId(), theme);
final ImageView iconView = (ImageView) holder.findViewById(R.id.banner_icon);
if (iconView != null) {
@@ -211,9 +223,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
} else {
iconView.setVisibility(View.VISIBLE);
iconView.setImageDrawable(
- icon == null
- ? getContext().getDrawable(R.drawable.ic_warning)
- : icon);
+ icon == null ? context.getDrawable(R.drawable.ic_warning) : icon);
if (mAttentionLevel != AttentionLevel.NORMAL
&& !SettingsThemeHelper.isExpressiveTheme(context)) {
iconView.setColorFilter(
@@ -224,14 +234,24 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
if (IS_AT_LEAST_S) {
@ColorInt final int backgroundColor =
- context.getResources().getColor(
- mAttentionLevel.getBackgroundColorResId(), theme);
-
- @ColorInt final int btnBackgroundColor =
- context.getResources().getColor(mAttentionLevel.getButtonBackgroundColorResId(),
- theme);
- ColorStateList strokeColor = context.getResources().getColorStateList(
- mAttentionLevel.getButtonBackgroundColorResId(), theme);
+ resources.getColor(mAttentionLevel.getBackgroundColorResId(), theme);
+
+ ColorStateList btnBackgroundColor =
+ resources.getColorStateList(
+ mAttentionLevel.getButtonBackgroundColorResId(), theme);
+ ColorStateList btnStrokeColor =
+ mAttentionLevel == AttentionLevel.NORMAL
+ ? resources.getColorStateList(
+ R.color.settingslib_banner_outline_button_stroke_normal, theme)
+ : btnBackgroundColor;
+ ColorStateList filledBtnTextColor =
+ resources.getColorStateList(
+ mAttentionLevel.getButtonContentColorResId(), theme);
+ ColorStateList outlineBtnTextColor =
+ mAttentionLevel == AttentionLevel.NORMAL
+ ? btnBackgroundColor
+ : resources.getColorStateList(
+ R.color.settingslib_banner_outline_button_content, theme);
holder.setDividerAllowedAbove(false);
holder.setDividerAllowedBelow(false);
@@ -242,10 +262,10 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
mPositiveButtonInfo.mColor = accentColor;
mNegativeButtonInfo.mColor = accentColor;
- if (mAttentionLevel != AttentionLevel.NORMAL) {
- mPositiveButtonInfo.mBackgroundColor = btnBackgroundColor;
- mNegativeButtonInfo.mStrokeColor = strokeColor;
- }
+ mPositiveButtonInfo.mBackgroundColor = btnBackgroundColor;
+ mPositiveButtonInfo.mTextColor = filledBtnTextColor;
+ mNegativeButtonInfo.mStrokeColor = btnStrokeColor;
+ mNegativeButtonInfo.mTextColor = outlineBtnTextColor;
mDismissButtonInfo.mButton = (ImageButton) holder.findViewById(R.id.banner_dismiss_btn);
mDismissButtonInfo.setUpButton();
@@ -261,8 +281,6 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
headerView.setText(mHeader);
headerView.setVisibility(TextUtils.isEmpty(mHeader) ? View.GONE : View.VISIBLE);
}
-
-
} else {
holder.setDividerAllowedAbove(true);
holder.setDividerAllowedBelow(true);
@@ -567,8 +585,9 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
private boolean mIsVisible = true;
private boolean mIsEnabled = true;
@ColorInt private int mColor;
- @ColorInt private int mBackgroundColor;
+ @Nullable private ColorStateList mBackgroundColor;
@Nullable private ColorStateList mStrokeColor;
+ @Nullable private ColorStateList mTextColor;
void setUpButton() {
if (mButton == null) {
@@ -586,12 +605,15 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
if (IS_AT_LEAST_S) {
if (btn != null && SettingsThemeHelper.isExpressiveTheme(btn.getContext())) {
- if (mBackgroundColor != 0) {
- btn.setBackgroundColor(mBackgroundColor);
+ if (mBackgroundColor != null) {
+ btn.setBackgroundTintList(mBackgroundColor);
}
if (mStrokeColor != null) {
btn.setStrokeColor(mStrokeColor);
}
+ if (mTextColor != null) {
+ btn.setTextColor(mTextColor);
+ }
} else {
mButton.setTextColor(mColor);
}
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_button_background_filled.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_button_background_filled.xml
new file mode 100644
index 000000000000..68cc058c5974
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_button_background_filled.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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/settingslib_materialColorPrimary"/>
+ <corners android:radius="@dimen/settingslib_expressive_radius_full"/>
+</shape> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_button_background_outline.xml b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_button_background_outline.xml
new file mode 100644
index 000000000000..213289d5158b
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/res/drawable/settingslib_expressive_button_background_outline.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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <stroke android:width="1dp"
+ android:color="@color/settingslib_materialColorOutlineVariant"/>
+ <corners android:radius="@dimen/settingslib_expressive_radius_full"/>
+</shape> \ No newline at end of file
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v36/styles_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v36/styles_expressive.xml
index 3af88c48e8ca..de48f99215fb 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v36/styles_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v36/styles_expressive.xml
@@ -90,4 +90,39 @@
parent="@style/TextAppearance.SettingsLib.BodyMedium">
<item name="android:textColor">@color/settingslib_text_color_secondary</item>
</style>
+
+ <style name="Widget.SettingsLib.DialogWindowTitle" parent="">
+ <item name="android:scrollHorizontally">false</item>
+ <item name="android:ellipsize">end</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.HeadlineSmall</item>
+ </style>
+
+ <style name="Widget.SettingsLib.ButtonBar" parent="@style/Widget.AppCompat.ButtonBar.AlertDialog">
+ <item name="android:paddingTop">@dimen/settingslib_expressive_space_extrasmall2</item>
+ <item name="android:paddingBottom">@dimen/settingslib_expressive_space_small4</item>
+ </style>
+
+ <style name="Widget.SettingsLib.DialogButton" parent="@style/Widget.AppCompat.Button">
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:minHeight">0dp</item>
+ <item name="android:minWidth">0dp</item>
+ <item name="android:paddingVertical">@dimen/settingslib_expressive_space_extrasmall5</item>
+ <item name="android:paddingHorizontal">@dimen/settingslib_expressive_space_small1</item>
+ <item name="android:stateListAnimator">@null</item>
+ <item name="textAllCaps">false</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.LabelLarge</item>
+ <item name="android:textColor">@color/settingslib_materialColorPrimary</item>
+ <item name="android:background">@android:color/transparent</item>
+ </style>
+
+ <style name="Widget.SettingsLib.DialogButton.Filled">
+ <item name="android:layout_marginEnd">@dimen/settingslib_expressive_space_extrasmall6</item>
+ <item name="android:background">@drawable/settingslib_expressive_button_background_filled</item>
+ <item name="android:textColor">@color/settingslib_materialColorOnPrimary</item>
+ </style>
+
+ <style name="Widget.SettingsLib.DialogButton.Outline">
+ <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
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v36/themes_expressive.xml b/packages/SettingsLib/SettingsTheme/res/values-v36/themes_expressive.xml
index 14f214a96435..5173ebeaa9a1 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v36/themes_expressive.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v36/themes_expressive.xml
@@ -53,6 +53,17 @@
<item name="colorControlNormal">?android:attr/colorControlNormal</item>
<!-- For AndroidX AlertDialog -->
- <!--item name="alertDialogTheme">@style/Theme.AlertDialog.SettingsLib</item-->
+ <item name="alertDialogTheme">@style/Theme.AlertDialog.SettingsLib.Expressive</item>
+ </style>
+
+ <style name="Theme.AlertDialog.SettingsLib.Expressive">
+ <item name="colorAccent">@color/settingslib_materialColorPrimary</item>
+ <item name="android:colorBackground">@color/settingslib_materialColorSurfaceContainerHigh</item>
+ <item name="android:windowTitleStyle">@style/Widget.SettingsLib.DialogWindowTitle</item>
+ <item name="dialogPreferredPadding">@dimen/settingslib_expressive_space_small4</item>
+ <item name="buttonBarStyle">@style/Widget.SettingsLib.ButtonBar</item>
+ <item name="buttonBarPositiveButtonStyle">@style/Widget.SettingsLib.DialogButton.Filled</item>
+ <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
diff --git a/packages/SettingsLib/res/xml/timezones.xml b/packages/SettingsLib/res/xml/timezones.xml
index 6a8d7802f9fd..4cea32aa05f9 100644
--- a/packages/SettingsLib/res/xml/timezones.xml
+++ b/packages/SettingsLib/res/xml/timezones.xml
@@ -35,6 +35,7 @@
<timezone id="Europe/Brussels"></timezone>
<timezone id="Europe/Madrid"></timezone>
<timezone id="Europe/Sarajevo"></timezone>
+ <timezone id="Europe/Warsaw"></timezone>
<timezone id="Africa/Windhoek"></timezone>
<timezone id="Africa/Brazzaville"></timezone>
<timezone id="Asia/Amman"></timezone>
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
index 2ed437c94439..cca43b92ef19 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiUtils.kt
@@ -487,7 +487,7 @@ open class WifiUtils {
context,
lifecycleOwner.lifecycleScope,
ssid,
- WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW,
+ WindowManager.LayoutParams.TYPE_APPLICATION,
{ intent -> context.startActivity(intent) },
onAllowed
)
@@ -510,9 +510,9 @@ open class WifiUtils {
AdvancedProtectionManager.FEATURE_ID_DISALLOW_WEP,
AdvancedProtectionManager.SUPPORT_DIALOG_TYPE_BLOCKED_INTERACTION)
intent.putExtra(DIALOG_WINDOW_TYPE, dialogWindowType)
- onStartActivity(intent)
+ withContext(Dispatchers.Main) { onStartActivity(intent) }
} else if (wifiManager.isWepSupported == true && wifiManager.queryWepAllowed()) {
- onAllowed()
+ withContext(Dispatchers.Main) { onAllowed() }
} else {
val intent = Intent(Intent.ACTION_MAIN).apply {
component = ComponentName(
@@ -522,7 +522,7 @@ open class WifiUtils {
putExtra(DIALOG_WINDOW_TYPE, dialogWindowType)
putExtra(SSID, ssid)
}.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- onStartActivity(intent)
+ withContext(Dispatchers.Main) { onStartActivity(intent) }
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
index 7f4bdaeac855..83471ae9513e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/BannerMessagePreferenceTest.java
@@ -18,6 +18,7 @@ package com.android.settingslib.widget;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.robolectric.Robolectric.setupActivity;
@@ -25,6 +26,8 @@ import static org.robolectric.Shadows.shadowOf;
import android.app.Activity;
import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
@@ -38,24 +41,34 @@ import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.ColorRes;
+import androidx.annotation.NonNull;
import androidx.preference.PreferenceViewHolder;
-import com.android.settingslib.testutils.OverpoweredReflectionHelper;
import com.android.settingslib.widget.preference.banner.R;
+import com.google.android.material.button.MaterialButton;
+
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
import org.robolectric.shadows.ShadowDrawable;
import org.robolectric.shadows.ShadowTouchDelegate;
import org.robolectric.util.ReflectionHelpers;
-@Ignore("b/359066481")
+import java.util.List;
+
@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {BannerMessagePreferenceTest.ShadowSettingsThemeHelper.class})
public class BannerMessagePreferenceTest {
private Context mContext;
@@ -66,14 +79,23 @@ public class BannerMessagePreferenceTest {
private boolean mClickListenerCalled = false;
private final View.OnClickListener mClickListener = v -> mClickListenerCalled = true;
private final int mMinimumTargetSize =
- RuntimeEnvironment.application.getResources()
- .getDimensionPixelSize(com.android.settingslib.widget.theme.R.dimen.settingslib_preferred_minimum_touch_target);
+ RuntimeEnvironment.application
+ .getResources()
+ .getDimensionPixelSize(
+ com.android.settingslib.widget.theme.R.dimen
+ .settingslib_preferred_minimum_touch_target);
- private static final int TEST_STRING_RES_ID =
- R.string.accessibility_banner_message_dismiss;
+ private static final int TEST_STRING_RES_ID = R.string.accessibility_banner_message_dismiss;
+
+ @Mock private View mMockBackgroundView;
+ @Mock private Drawable mMockCardBackground;
+ @Mock private MaterialButton mMockPositiveBtn;
+ @Mock private MaterialButton mMockNegativeBtn;
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ ShadowSettingsThemeHelper.setExpressiveTheme(false);
mContext = RuntimeEnvironment.application;
mClickListenerCalled = false;
mBannerPreference = new BannerMessagePreference(mContext);
@@ -90,6 +112,7 @@ public class BannerMessagePreferenceTest {
.isEqualTo("test");
}
+ @Ignore("b/359066481")
@Test
public void onBindViewHolder_andOnLayoutView_dismissButtonTouchDelegate_isCorrectSize() {
assumeAndroidS();
@@ -155,9 +178,8 @@ public class BannerMessagePreferenceTest {
@Test
public void onBindViewHolder_whenAtLeastS_whenSubtitleXmlAttribute_shouldSetSubtitle() {
assumeAndroidS();
- AttributeSet mAttributeSet = Robolectric.buildAttributeSet()
- .addAttribute(R.attr.subtitle, "Test")
- .build();
+ AttributeSet mAttributeSet =
+ Robolectric.buildAttributeSet().addAttribute(R.attr.subtitle, "Test").build();
mBannerPreference = new BannerMessagePreference(mContext, mAttributeSet);
mBannerPreference.onBindViewHolder(mHolder);
@@ -185,8 +207,7 @@ public class BannerMessagePreferenceTest {
ImageView mIcon = mRootView.findViewById(R.id.banner_icon);
ShadowDrawable shadowDrawable = shadowOf(mIcon.getDrawable());
- assertThat(shadowDrawable.getCreatedFromResId())
- .isEqualTo(R.drawable.settingslib_ic_cross);
+ assertThat(shadowDrawable.getCreatedFromResId()).isEqualTo(R.drawable.settingslib_ic_cross);
}
@Test
@@ -207,6 +228,7 @@ public class BannerMessagePreferenceTest {
Button mPositiveButton = mRootView.findViewById(R.id.banner_positive_btn);
assertThat(mPositiveButton.getVisibility()).isEqualTo(View.VISIBLE);
+
assertThat(mPositiveButton.getText()).isEqualTo(mContext.getString(TEST_STRING_RES_ID));
}
@@ -218,6 +240,7 @@ public class BannerMessagePreferenceTest {
Button mNegativeButton = mRootView.findViewById(R.id.banner_negative_btn);
assertThat(mNegativeButton.getVisibility()).isEqualTo(View.VISIBLE);
+
assertThat(mNegativeButton.getText()).isEqualTo(mContext.getString(TEST_STRING_RES_ID));
}
@@ -359,8 +382,6 @@ public class BannerMessagePreferenceTest {
@Test
public void onBindViewHolder_whenAtLeastS_whenAttentionUnset_setsHighTheme() {
assumeAndroidS();
- Drawable mCardBackgroundSpy = spy(mRootView.getBackground());
- mRootView.setBackground(mCardBackgroundSpy);
mBannerPreference.onBindViewHolder(mHolder);
@@ -370,17 +391,15 @@ public class BannerMessagePreferenceTest {
.isEqualTo(getColorId(R.color.banner_accent_attention_high));
assertThat(getButtonColor(R.id.banner_negative_btn))
.isEqualTo(getColorId(R.color.banner_accent_attention_high));
- verify(mCardBackgroundSpy).setTint(getColorId(R.color.banner_background_attention_high));
+
+ verify(mMockCardBackground).setTint(getColorId(R.color.banner_background_attention_high));
}
@Test
public void onBindViewHolder_whenAtLeastS_whenAttentionHighByXML_setsHighTheme() {
assumeAndroidS();
- Drawable mCardBackgroundSpy = spy(mRootView.getBackground());
- mRootView.setBackground(mCardBackgroundSpy);
- AttributeSet mAttributeSet = Robolectric.buildAttributeSet()
- .addAttribute(R.attr.attentionLevel, "high")
- .build();
+ AttributeSet mAttributeSet =
+ Robolectric.buildAttributeSet().addAttribute(R.attr.attentionLevel, "high").build();
mBannerPreference = new BannerMessagePreference(mContext, mAttributeSet);
mBannerPreference.onBindViewHolder(mHolder);
@@ -391,17 +410,17 @@ public class BannerMessagePreferenceTest {
.isEqualTo(getColorId(R.color.banner_accent_attention_high));
assertThat(getButtonColor(R.id.banner_negative_btn))
.isEqualTo(getColorId(R.color.banner_accent_attention_high));
- verify(mCardBackgroundSpy).setTint(getColorId(R.color.banner_background_attention_high));
+
+ verify(mMockCardBackground).setTint(getColorId(R.color.banner_background_attention_high));
}
@Test
public void onBindViewHolder_whenAtLeastS_whenAttentionMediumByXML_setsMediumTheme() {
assumeAndroidS();
- Drawable mCardBackgroundSpy = spy(mRootView.getBackground());
- mRootView.setBackground(mCardBackgroundSpy);
- AttributeSet mAttributeSet = Robolectric.buildAttributeSet()
- .addAttribute(R.attr.attentionLevel, "medium")
- .build();
+ AttributeSet mAttributeSet =
+ Robolectric.buildAttributeSet()
+ .addAttribute(R.attr.attentionLevel, "medium")
+ .build();
mBannerPreference = new BannerMessagePreference(mContext, mAttributeSet);
mBannerPreference.onBindViewHolder(mHolder);
@@ -412,17 +431,15 @@ public class BannerMessagePreferenceTest {
.isEqualTo(getColorId(R.color.banner_accent_attention_medium));
assertThat(getButtonColor(R.id.banner_negative_btn))
.isEqualTo(getColorId(R.color.banner_accent_attention_medium));
- verify(mCardBackgroundSpy).setTint(getColorId(R.color.banner_background_attention_medium));
+
+ verify(mMockCardBackground).setTint(getColorId(R.color.banner_background_attention_medium));
}
@Test
public void onBindViewHolder_whenAtLeastS_whenAttentionLowByXML_setsLowTheme() {
assumeAndroidS();
- Drawable mCardBackgroundSpy = spy(mRootView.getBackground());
- mRootView.setBackground(mCardBackgroundSpy);
- AttributeSet mAttributeSet = Robolectric.buildAttributeSet()
- .addAttribute(R.attr.attentionLevel, "low")
- .build();
+ AttributeSet mAttributeSet =
+ Robolectric.buildAttributeSet().addAttribute(R.attr.attentionLevel, "low").build();
mBannerPreference = new BannerMessagePreference(mContext, mAttributeSet);
mBannerPreference.onBindViewHolder(mHolder);
@@ -433,14 +450,13 @@ public class BannerMessagePreferenceTest {
.isEqualTo(getColorId(R.color.banner_accent_attention_low));
assertThat(getButtonColor(R.id.banner_negative_btn))
.isEqualTo(getColorId(R.color.banner_accent_attention_low));
- verify(mCardBackgroundSpy).setTint(getColorId(R.color.banner_background_attention_low));
+
+ verify(mMockCardBackground).setTint(getColorId(R.color.banner_background_attention_low));
}
@Test
public void setAttentionLevel_whenAtLeastS_whenHighAttention_setsHighTheme() {
assumeAndroidS();
- Drawable mCardBackgroundSpy = spy(mRootView.getBackground());
- mRootView.setBackground(mCardBackgroundSpy);
mBannerPreference.setAttentionLevel(BannerMessagePreference.AttentionLevel.HIGH);
mBannerPreference.onBindViewHolder(mHolder);
@@ -451,14 +467,44 @@ public class BannerMessagePreferenceTest {
.isEqualTo(getColorId(R.color.banner_accent_attention_high));
assertThat(getButtonColor(R.id.banner_negative_btn))
.isEqualTo(getColorId(R.color.banner_accent_attention_high));
- verify(mCardBackgroundSpy).setTint(getColorId(R.color.banner_background_attention_high));
+
+ verify(mMockCardBackground).setTint(getColorId(R.color.banner_background_attention_high));
}
@Test
- public void setAttentionLevel_whenAtLeastS_whenMedAttention_setsMediumTheme() {
+ public void setAttentionLevel_whenAtLeastS_whenHighAttentionAndExpressiveTheme_setsBtnTheme() {
+ setExpressiveTheme(true);
+ assumeAndroidS();
+ assertThat(SettingsThemeHelper.isExpressiveTheme(mContext)).isTrue();
+ assertThat(SettingsThemeHelper.isExpressiveTheme(mContext)).isTrue();
+ doReturn(mMockPositiveBtn).when(mHolder).findViewById(R.id.banner_positive_btn);
+ doReturn(mMockNegativeBtn).when(mHolder).findViewById(R.id.banner_negative_btn);
+ assertThat(SettingsThemeHelper.isExpressiveTheme(mContext)).isTrue();
+ mBannerPreference.setAttentionLevel(BannerMessagePreference.AttentionLevel.HIGH);
+ final ArgumentCaptor<ColorStateList> captor = ArgumentCaptor.forClass(ColorStateList.class);
+ ColorStateList filledBtnBackground =
+ getColorStateList(R.color.settingslib_banner_button_background_high);
+ ColorStateList filledBtnTextColor =
+ getColorStateList(R.color.settingslib_banner_filled_button_content_high);
+ ColorStateList outlineBtnTextColor =
+ getColorStateList(R.color.settingslib_banner_outline_button_content);
+
+ mBannerPreference.onBindViewHolder(mHolder);
+
+ verify(mMockPositiveBtn).setBackgroundTintList(captor.capture());
+ verify(mMockPositiveBtn).setTextColor(captor.capture());
+ verify(mMockNegativeBtn).setStrokeColor(captor.capture());
+ verify(mMockNegativeBtn).setTextColor(captor.capture());
+ List<ColorStateList> colors = captor.getAllValues();
+ assertThat(colors.get(0).getColors()).isEqualTo(filledBtnBackground.getColors());
+ assertThat(colors.get(1).getColors()).isEqualTo(filledBtnTextColor.getColors());
+ assertThat(colors.get(2).getColors()).isEqualTo(filledBtnBackground.getColors());
+ assertThat(colors.get(3).getColors()).isEqualTo(outlineBtnTextColor.getColors());
+ }
+
+ @Test
+ public void setAttentionLevel_whenAtLeastS_whenMedAttention_setsBtnMediumTheme() {
assumeAndroidS();
- Drawable mCardBackgroundSpy = spy(mRootView.getBackground());
- mRootView.setBackground(mCardBackgroundSpy);
mBannerPreference.setAttentionLevel(BannerMessagePreference.AttentionLevel.MEDIUM);
mBannerPreference.onBindViewHolder(mHolder);
@@ -469,14 +515,42 @@ public class BannerMessagePreferenceTest {
.isEqualTo(getColorId(R.color.banner_accent_attention_medium));
assertThat(getButtonColor(R.id.banner_negative_btn))
.isEqualTo(getColorId(R.color.banner_accent_attention_medium));
- verify(mCardBackgroundSpy).setTint(getColorId(R.color.banner_background_attention_medium));
+
+ verify(mMockCardBackground).setTint(getColorId(R.color.banner_background_attention_medium));
+ }
+
+ @Test
+ public void setAttentionLevel_whenAtLeastS_whenMedAttentionAndExpressiveTheme_setsBtnTheme() {
+ setExpressiveTheme(true);
+ mContext.getResources().getConfiguration().uiMode = Configuration.UI_MODE_NIGHT_NO;
+ assumeAndroidS();
+ doReturn(mMockPositiveBtn).when(mHolder).findViewById(R.id.banner_positive_btn);
+ doReturn(mMockNegativeBtn).when(mHolder).findViewById(R.id.banner_negative_btn);
+ mBannerPreference.setAttentionLevel(BannerMessagePreference.AttentionLevel.MEDIUM);
+ final ArgumentCaptor<ColorStateList> captor = ArgumentCaptor.forClass(ColorStateList.class);
+ ColorStateList filledBtnBackground =
+ getColorStateList(R.color.settingslib_banner_button_background_medium);
+ ColorStateList filledBtnTextColor =
+ getColorStateList(R.color.settingslib_banner_filled_button_content_medium);
+ ColorStateList outlineBtnTextColor =
+ getColorStateList(R.color.settingslib_banner_outline_button_content);
+
+ mBannerPreference.onBindViewHolder(mHolder);
+
+ verify(mMockPositiveBtn).setBackgroundTintList(captor.capture());
+ verify(mMockPositiveBtn).setTextColor(captor.capture());
+ verify(mMockNegativeBtn).setStrokeColor(captor.capture());
+ verify(mMockNegativeBtn).setTextColor(captor.capture());
+ List<ColorStateList> colors = captor.getAllValues();
+ assertThat(colors.get(0).getColors()).isEqualTo(filledBtnBackground.getColors());
+ assertThat(colors.get(1).getColors()).isEqualTo(filledBtnTextColor.getColors());
+ assertThat(colors.get(2).getColors()).isEqualTo(filledBtnBackground.getColors());
+ assertThat(colors.get(3).getColors()).isEqualTo(outlineBtnTextColor.getColors());
}
@Test
public void setAttentionLevel_whenAtLeastS_whenLowAttention_setsLowTheme() {
assumeAndroidS();
- Drawable mCardBackgroundSpy = spy(mRootView.getBackground());
- mRootView.setBackground(mCardBackgroundSpy);
mBannerPreference.setAttentionLevel(BannerMessagePreference.AttentionLevel.LOW);
mBannerPreference.onBindViewHolder(mHolder);
@@ -487,7 +561,37 @@ public class BannerMessagePreferenceTest {
.isEqualTo(getColorId(R.color.banner_accent_attention_low));
assertThat(getButtonColor(R.id.banner_negative_btn))
.isEqualTo(getColorId(R.color.banner_accent_attention_low));
- verify(mCardBackgroundSpy).setTint(getColorId(R.color.banner_background_attention_low));
+ verify(mMockCardBackground).setTint(getColorId(R.color.banner_background_attention_low));
+ }
+
+ @Test
+ public void
+ setAttentionLevel_whenAtLeastS_whenNormalAttentionAndExpressiveTheme_setsBtnTheme() {
+ setExpressiveTheme(true);
+ mContext.getResources().getConfiguration().uiMode = Configuration.UI_MODE_NIGHT_NO;
+ assumeAndroidS();
+ doReturn(mMockPositiveBtn).when(mHolder).findViewById(R.id.banner_positive_btn);
+ doReturn(mMockNegativeBtn).when(mHolder).findViewById(R.id.banner_negative_btn);
+ mBannerPreference.setAttentionLevel(BannerMessagePreference.AttentionLevel.NORMAL);
+ final ArgumentCaptor<ColorStateList> captor = ArgumentCaptor.forClass(ColorStateList.class);
+ ColorStateList filledBtnBackground =
+ getColorStateList(R.color.settingslib_banner_button_background_normal);
+ ColorStateList filledBtnTextColor =
+ getColorStateList(R.color.settingslib_banner_filled_button_content_normal);
+ ColorStateList outlineBtnStrokeColor =
+ getColorStateList(R.color.settingslib_banner_outline_button_stroke_normal);
+
+ mBannerPreference.onBindViewHolder(mHolder);
+
+ verify(mMockPositiveBtn).setBackgroundTintList(captor.capture());
+ verify(mMockPositiveBtn).setTextColor(captor.capture());
+ verify(mMockNegativeBtn).setStrokeColor(captor.capture());
+ verify(mMockNegativeBtn).setTextColor(captor.capture());
+ List<ColorStateList> colors = captor.getAllValues();
+ assertThat(colors.get(0).getColors()).isEqualTo(filledBtnBackground.getColors());
+ assertThat(colors.get(1).getColors()).isEqualTo(filledBtnTextColor.getColors());
+ assertThat(colors.get(2).getColors()).isEqualTo(outlineBtnStrokeColor.getColors());
+ assertThat(colors.get(3).getColors()).isEqualTo(filledBtnBackground.getColors());
}
private int getButtonColor(int buttonResId) {
@@ -495,6 +599,11 @@ public class BannerMessagePreferenceTest {
return mButton.getTextColors().getDefaultColor();
}
+ private ColorStateList getButtonTextColor(int buttonResId) {
+ Button mButton = mRootView.findViewById(buttonResId);
+ return mButton.getTextColors();
+ }
+
private ColorFilter getColorFilter(@ColorRes int colorResId) {
return new PorterDuffColorFilter(getColorId(colorResId), PorterDuff.Mode.SRC_IN);
}
@@ -503,28 +612,57 @@ public class BannerMessagePreferenceTest {
return mContext.getResources().getColor(colorResId, mContext.getTheme());
}
+ private ColorStateList getColorStateList(@ColorRes int colorResId) {
+ return mContext.getResources().getColorStateList(colorResId, mContext.getTheme());
+ }
+
private void assumeAndroidR() {
ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 30);
ReflectionHelpers.setStaticField(Build.VERSION.class, "CODENAME", "R");
- OverpoweredReflectionHelper
- .setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", false);
- // Reset view holder to use correct layout.
- }
-
+ // Refresh the static final field IS_AT_LEAST_S
+ mBannerPreference = new BannerMessagePreference(mContext);
+ setUpViewHolder();
+ }
private void assumeAndroidS() {
ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", 31);
ReflectionHelpers.setStaticField(Build.VERSION.class, "CODENAME", "S");
- OverpoweredReflectionHelper
- .setStaticField(BannerMessagePreference.class, "IS_AT_LEAST_S", true);
- // Re-inflate view to update layout.
+
+ // Refresh the static final field IS_AT_LEAST_S
+ mBannerPreference = new BannerMessagePreference(mContext);
setUpViewHolder();
}
+ private void setExpressiveTheme(boolean isExpressiveTheme) {
+ ShadowSettingsThemeHelper.setExpressiveTheme(isExpressiveTheme);
+ assertThat(SettingsThemeHelper.isExpressiveTheme(mContext)).isEqualTo(isExpressiveTheme);
+ if (isExpressiveTheme) {
+ doReturn(mContext).when(mMockPositiveBtn).getContext();
+ doReturn(mContext).when(mMockNegativeBtn).getContext();
+ }
+ }
+
private void setUpViewHolder() {
mRootView =
View.inflate(mContext, mBannerPreference.getLayoutResource(), null /* parent */);
- mHolder = PreferenceViewHolder.createInstanceForTests(mRootView);
+ mHolder = spy(PreferenceViewHolder.createInstanceForTests(mRootView));
+ doReturn(mMockBackgroundView).when(mHolder).findViewById(R.id.banner_background);
+ doReturn(mMockCardBackground).when(mMockBackgroundView).getBackground();
+ }
+
+ @Implements(SettingsThemeHelper.class)
+ public static class ShadowSettingsThemeHelper {
+ private static boolean sIsExpressiveTheme;
+
+ /** Shadow implementation of isExpressiveTheme */
+ @Implementation
+ public static boolean isExpressiveTheme(@NonNull Context context) {
+ return sIsExpressiveTheme;
+ }
+
+ static void setExpressiveTheme(boolean isExpressiveTheme) {
+ sIsExpressiveTheme = isExpressiveTheme;
+ }
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
index d8b6707b9118..97473fffaeb1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiUtilsTest.java
@@ -50,6 +50,7 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
@@ -211,10 +212,12 @@ public class WifiUtilsTest {
WifiUtils.InternetIconInjector iconInjector = new WifiUtils.InternetIconInjector(mContext);
for (int level = 0; level <= 4; level++) {
+ Mockito.reset(mContext);
iconInjector.getIcon(false /* noInternet */, level);
verify(mContext).getDrawable(
WifiUtils.getInternetIconResource(level, false /* noInternet */));
+ Mockito.reset(mContext);
iconInjector.getIcon(true /* noInternet */, level);
verify(mContext).getDrawable(
WifiUtils.getInternetIconResource(level, true /* noInternet */));
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 527a1f16a84f..5bbfdf7bab81 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -672,6 +672,7 @@ public class SettingsHelper {
public static LocaleList resolveLocales(LocaleList restore, LocaleList current,
String[] supportedLocales) {
final HashMap<Locale, Locale> allLocales = new HashMap<>(supportedLocales.length);
+ final HashSet<String> existingLanguageAndScript = new HashSet<>();
for (String supportedLocaleStr : supportedLocales) {
final Locale locale = Locale.forLanguageTag(supportedLocaleStr);
allLocales.put(toFullLocale(locale), locale);
@@ -679,30 +680,26 @@ public class SettingsHelper {
// After restoring to reset locales, need to get extensions from restored locale. Get the
// first restored locale to check its extension.
- final Locale restoredLocale = restore.isEmpty()
+ final Locale firstRestoredLocale = restore.isEmpty()
? Locale.ROOT
: restore.get(0);
final ArrayList<Locale> filtered = new ArrayList<>(current.size());
for (int i = 0; i < current.size(); i++) {
- Locale locale = copyExtensionToTargetLocale(restoredLocale, current.get(i));
- allLocales.remove(toFullLocale(locale));
- filtered.add(locale);
+ Locale locale = copyExtensionToTargetLocale(firstRestoredLocale, current.get(i));
+
+ if (locale != null && existingLanguageAndScript.add(getLanguageAndScript(locale))) {
+ allLocales.remove(toFullLocale(locale));
+ filtered.add(locale);
+ }
}
- final HashSet<String> existingLanguageAndScript = new HashSet<>();
for (int i = 0; i < restore.size(); i++) {
- final Locale restoredLocaleWithExtension = copyExtensionToTargetLocale(restoredLocale,
- getFilteredLocale(restore.get(i), allLocales));
-
- if (restoredLocaleWithExtension != null) {
- String language = restoredLocaleWithExtension.getLanguage();
- String script = restoredLocaleWithExtension.getScript();
+ final Locale restoredLocaleWithExtension = copyExtensionToTargetLocale(
+ firstRestoredLocale, getFilteredLocale(restore.get(i), allLocales));
- String restoredLanguageAndScript =
- script == null ? language : language + "-" + script;
- if (existingLanguageAndScript.add(restoredLanguageAndScript)) {
- filtered.add(restoredLocaleWithExtension);
- }
+ if (restoredLocaleWithExtension != null && existingLanguageAndScript.add(
+ getLanguageAndScript(restoredLocaleWithExtension))) {
+ filtered.add(restoredLocaleWithExtension);
}
}
@@ -713,6 +710,16 @@ public class SettingsHelper {
return new LocaleList(filtered.toArray(new Locale[filtered.size()]));
}
+ private static String getLanguageAndScript(Locale locale) {
+ if (locale == null) {
+ return "";
+ }
+
+ String language = locale.getLanguage();
+ String script = locale.getScript();
+ return script == null ? language : String.join("-", language, script);
+ }
+
private static Locale copyExtensionToTargetLocale(Locale restoredLocale,
Locale targetLocale) {
if (!restoredLocale.hasExtensions()) {
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
index 48c778542d66..2160d3164b17 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
@@ -388,11 +388,18 @@ public class SettingsHelperTest {
LocaleList.forLanguageTags("zh-Hant-TW"), // current
new String[] { "fa-Arab-AF-u-nu-latn", "zh-Hant-TW" })); // supported
- assertEquals(LocaleList.forLanguageTags("en-US,zh-Hans-TW"),
+ assertEquals(LocaleList.forLanguageTags("en-US,zh-Hans-TW,fr-FR"),
SettingsHelper.resolveLocales(
- LocaleList.forLanguageTags("en-UK,en-GB,zh-Hans-HK"), // restore
- LocaleList.forLanguageTags("en-US,zh-Hans-TW"), // current
- new String[] { "en-US,zh-Hans-TW,en-UK,en-GB,zh-Hans-HK" })); // supported
+ // restore
+ LocaleList.forLanguageTags("en-UK,en-GB,zh-Hans-HK,fr-FR"),
+
+ // current
+ LocaleList.forLanguageTags("en-US,zh-Hans-TW"),
+
+ // supported
+ new String[] {
+ "en-US" , "zh-Hans-TW" , "en-UK", "en-GB", "zh-Hans-HK", "fr-FR"
+ }));
}
@Test
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 758ad797f761..a178869d23d6 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -349,7 +349,7 @@
<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_COMPUTER" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_NEARBY_DEVICE_STREAMING" />
- <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_SENSOR_DEVICE_STREAMING" />
+ <uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_VIRTUAL_DEVICE" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_PROFILE_GLASSES" />
<uses-permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index a93291f2db98..91492b2959d8 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -512,13 +512,6 @@ flag {
}
flag {
- name: "status_bar_notification_chips"
- namespace: "systemui"
- description: "Show promoted ongoing notifications as chips in the status bar"
- bug: "364653005"
-}
-
-flag {
name: "status_bar_popup_chips"
namespace: "systemui"
description: "Show rich ongoing processes as chips in the status bar"
@@ -542,6 +535,16 @@ flag {
}
flag {
+ name: "status_bar_window_no_custom_touch"
+ namespace: "systemui"
+ description: "Don't have any custom touch handling in StatusBarWindowView"
+ bug: "391894499"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "icon_refresh_2025"
namespace: "systemui"
description: "Build time flag for 2025 icon refresh"
@@ -1994,13 +1997,6 @@ flag {
}
flag {
- name: "notification_magic_actions_treatment"
- namespace: "systemui"
- description: "Special UI treatment for magic actions"
- bug: "383567383"
-}
-
-flag {
name: "notification_animated_actions_treatment"
namespace: "systemui"
description: "Special UI treatment for animated actions and replys"
@@ -2035,13 +2031,6 @@ flag {
}
flag {
- name: "ui_rich_ongoing_force_expanded"
- namespace: "systemui"
- description: "Force promoted notifications to always be expanded"
- bug: "380901479"
-}
-
-flag {
name: "permission_helper_ui_rich_ongoing"
namespace: "systemui"
description: "[RONs] Guards inline permission helper for demoting RONs [Guts/card version]"
@@ -2056,13 +2045,6 @@ flag {
}
flag {
- name: "aod_ui_rich_ongoing"
- namespace: "systemui"
- description: "Show a rich ongoing notification on the always-on display (depends on ui_rich_ongoing)"
- bug: "369151941"
-}
-
-flag {
name: "stabilize_heads_up_group_v2"
namespace: "systemui"
description: "Stabilize heads up groups in VisualStabilityCoordinator"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
index 440a81fc2152..0680faf5226a 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt
@@ -1483,7 +1483,8 @@ constructor(
// TODO(b/397646693): remove this exception.
val isEligibleForReparenting = controller.isLaunching
val viewRoot = controller.transitionContainer.viewRootImpl
- val skipReparenting = skipReparentTransaction || viewRoot == null
+ val skipReparenting =
+ skipReparentTransaction || !window.leash.isValid || viewRoot == null
if (moveTransitionAnimationLayer() && isEligibleForReparenting && !skipReparenting) {
reparent = true
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
index 0181928317e1..1a0fb0afd385 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContent.kt
@@ -16,6 +16,7 @@
package com.android.systemui.communal.ui.compose
+import android.content.res.Configuration
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -23,6 +24,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntRect
@@ -66,6 +68,7 @@ constructor(
@Composable
fun ContentScope.Content(modifier: Modifier = Modifier) {
CommunalTouchableSurface(viewModel = viewModel, modifier = modifier) {
+ val orientation = LocalConfiguration.current.orientation
Layout(
modifier = Modifier.fillMaxSize(),
content = {
@@ -150,13 +153,29 @@ constructor(
val bottomAreaPlaceable = bottomAreaMeasurable.measure(noMinConstraints)
+ val communalGridMaxHeight: Int
+ val communalGridPositionY: Int
+ if (Flags.communalResponsiveGrid()) {
+ val communalGridVerticalMargin = constraints.maxHeight - lockIconBounds.top
+ // Bias the widgets up by a small offset for visual balance in landscape
+ // orientation
+ val verticalOffset =
+ (if (orientation == Configuration.ORIENTATION_LANDSCAPE) (-3).dp else 0.dp)
+ .roundToPx()
+ // Use even top and bottom margin for grid to be centered in maxHeight (window)
+ communalGridMaxHeight = constraints.maxHeight - communalGridVerticalMargin * 2
+ communalGridPositionY = communalGridVerticalMargin + verticalOffset
+ } else {
+ communalGridMaxHeight = lockIconBounds.top
+ communalGridPositionY = 0
+ }
val communalGridPlaceable =
communalGridMeasurable.measure(
- noMinConstraints.copy(maxHeight = lockIconBounds.top)
+ noMinConstraints.copy(maxHeight = communalGridMaxHeight)
)
layout(constraints.maxWidth, constraints.maxHeight) {
- communalGridPlaceable.place(x = 0, y = 0)
+ communalGridPlaceable.place(x = 0, y = communalGridPositionY)
lockIconPlaceable.place(x = lockIconBounds.left, y = lockIconBounds.top)
val bottomAreaTop = constraints.maxHeight - bottomAreaPlaceable.height
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 835dd7aa9f24..ad2a32e030bb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -54,7 +54,10 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.calculateStartPadding
+import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -76,8 +79,8 @@ import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
@@ -99,6 +102,8 @@ import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
+import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass
+import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
@@ -174,6 +179,7 @@ import com.android.compose.animation.Easings.Emphasized
import com.android.compose.animation.scene.ContentScope
import com.android.compose.modifiers.thenIf
import com.android.compose.ui.graphics.painter.rememberDrawablePainter
+import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.internal.R.dimen.system_app_widget_background_radius
import com.android.systemui.Flags
import com.android.systemui.Flags.communalResponsiveGrid
@@ -254,6 +260,7 @@ fun CommunalHub(
val windowMetrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
val screenWidth = windowMetrics.bounds.width()
val layoutDirection = LocalLayoutDirection.current
+
if (viewModel.isEditMode) {
ObserveNewWidgetAddedEffect(communalContent, gridState, viewModel)
} else {
@@ -757,11 +764,33 @@ fun calculateWidgetSize(
}
@Composable
+private fun horizontalPaddingWithInsets(padding: Dp): Dp {
+ val orientation = LocalConfiguration.current.orientation
+ val displayCutoutPaddings = WindowInsets.displayCutout.asPaddingValues()
+ val horizontalDisplayCutoutPadding =
+ remember(orientation, displayCutoutPaddings) {
+ if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ maxOf(
+ // Top in portrait becomes startPadding (or endPadding) in landscape
+ displayCutoutPaddings.calculateTopPadding(),
+ // Bottom in portrait becomes endPadding (or startPadding) in landscape
+ displayCutoutPaddings.calculateBottomPadding(),
+ )
+ } else {
+ 0.dp
+ }
+ }
+ return padding + horizontalDisplayCutoutPadding
+}
+
+@Composable
private fun HorizontalGridWrapper(
minContentPadding: PaddingValues,
gridState: LazyGridState,
dragDropState: GridDragDropState?,
setContentOffset: (offset: Offset) -> Unit,
+ minHorizontalArrangement: Dp,
+ minVerticalArrangement: Dp,
modifier: Modifier = Modifier,
content: LazyGridScope.(sizeInfo: SizeInfo?) -> Unit,
) {
@@ -775,8 +804,8 @@ private fun HorizontalGridWrapper(
state = gridState,
flingBehavior = flingBehavior,
minContentPadding = minContentPadding,
- minHorizontalArrangement = Dimensions.ItemSpacing,
- minVerticalArrangement = Dimensions.ItemSpacing,
+ minHorizontalArrangement = minHorizontalArrangement,
+ minVerticalArrangement = minVerticalArrangement,
setContentOffset = setContentOffset,
// Temporarily disable user gesture scrolling while dragging a widget to prevent
// conflicts between the drag and scroll gestures. Programmatic scrolling remains
@@ -833,6 +862,7 @@ private fun BoxScope.CommunalHubLazyGrid(
Modifier.align(Alignment.TopStart).onGloballyPositioned { setGridCoordinates(it) }
var list = communalContent
var dragDropState: GridDragDropState? = null
+ var arrangementSpacing = Dimensions.ItemSpacing
if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
list = contentListState.list
// for drag & drop operations within the communal hub grid
@@ -866,6 +896,9 @@ private fun BoxScope.CommunalHubLazyGrid(
Box(Modifier.fillMaxSize().dragAndDropTarget(dragAndDropTargetState)) {}
} else if (communalResponsiveGrid()) {
gridModifier = gridModifier.fillMaxSize()
+ if (isCompactWindow()) {
+ arrangementSpacing = Dimensions.ItemSpacingCompact
+ }
} else {
gridModifier = gridModifier.height(hubDimensions.GridHeight)
}
@@ -875,6 +908,8 @@ private fun BoxScope.CommunalHubLazyGrid(
gridState = gridState,
dragDropState = dragDropState,
minContentPadding = minContentPadding,
+ minHorizontalArrangement = arrangementSpacing,
+ minVerticalArrangement = arrangementSpacing,
setContentOffset = setContentOffset,
) { sizeInfo ->
/** Override spans based on the responsive grid size */
@@ -1839,11 +1874,21 @@ private fun nonScalableTextSize(sizeInDp: Dp) = with(LocalDensity.current) { siz
@Composable
private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): PaddingValues {
if (!isEditMode || toolbarSize == null) {
- return PaddingValues(
- start = Dimensions.ItemSpacing,
- end = Dimensions.ItemSpacing,
- top = hubDimensions.GridTopSpacing,
- )
+ return if (communalResponsiveGrid()) {
+ val horizontalPaddings: Dp =
+ if (isCompactWindow()) {
+ horizontalPaddingWithInsets(Dimensions.ItemSpacingCompact)
+ } else {
+ Dimensions.ItemSpacing
+ }
+ PaddingValues(start = horizontalPaddings, end = horizontalPaddings)
+ } else {
+ PaddingValues(
+ start = Dimensions.ItemSpacing,
+ end = Dimensions.ItemSpacing,
+ top = hubDimensions.GridTopSpacing,
+ )
+ }
}
val context = LocalContext.current
val density = LocalDensity.current
@@ -1870,6 +1915,16 @@ private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): Padd
}
}
+/** Compact size in landscape or portrait */
+@Composable
+fun isCompactWindow(): Boolean {
+ val windowSizeClass = LocalWindowSizeClass.current
+ return remember(windowSizeClass) {
+ windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact ||
+ windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact
+ }
+}
+
private fun CommunalContentSize.FixedSize.dp(): Dp {
return when (this) {
CommunalContentSize.FixedSize.FULL -> Dimensions.CardHeightFull
@@ -1911,6 +1966,9 @@ class Dimensions(val context: Context, val config: Configuration) {
val CardHeightFull
get() = 530.adjustedDp
+ val ItemSpacingCompact
+ get() = 12.adjustedDp
+
val ItemSpacing
get() = if (communalResponsiveGrid()) 32.adjustedDp else 50.adjustedDp
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
index 5fac6863e931..3e1252babee4 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/LockSection.kt
@@ -48,6 +48,7 @@ import com.android.systemui.log.dagger.LongPressTouchLog
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
+import com.google.android.msdl.domain.MSDLPlayer
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -66,6 +67,7 @@ constructor(
private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
private val falsingManager: Lazy<FalsingManager>,
private val vibratorHelper: Lazy<VibratorHelper>,
+ private val msdlPlayer: Lazy<MSDLPlayer>,
@LongPressTouchLog private val logBuffer: LogBuffer,
) {
@Composable
@@ -90,6 +92,7 @@ constructor(
deviceEntryBackgroundViewModel.get(),
falsingManager.get(),
vibratorHelper.get(),
+ msdlPlayer.get(),
overrideColor,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
index 454c15667f22..89d3060d020a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt
@@ -311,6 +311,20 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
assertThat(playSuccessHaptic).isNull()
}
+ @OptIn(ExperimentalCoroutinesApi::class)
+ @Test
+ fun playSuccessHaptic_onDeviceEntry_fromDeviceEntryIcon() =
+ testScope.runTest {
+ underTest = kosmos.deviceEntryHapticsInteractor
+ val playSuccessHaptic by collectLastValue(underTest.playSuccessHapticOnDeviceEntry)
+
+ kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
+ runCurrent()
+ kosmos.deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon()
+
+ assertThat(playSuccessHaptic).isNotNull()
+ }
+
// Mock dependencies for DeviceEntrySourceInteractor#deviceEntryFromBiometricSource
private fun configureDeviceEntryFromBiometricSource(
isFpUnlock: Boolean = false,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 6d4fffdefb1b..00710dc037fa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.scene.domain.startable
import android.app.StatusBarManager
@@ -121,6 +123,7 @@ import com.android.systemui.util.mockito.mock
import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository
import com.google.android.msdl.data.model.MSDLToken
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
@@ -2608,6 +2611,75 @@ class SceneContainerStartableTest : SysuiTestCase() {
}
@Test
+ fun handleDeviceUnlockStatus_returnsToLsFromBouncer_whenGoesToSleep() =
+ testScope.runTest {
+ val authMethod by collectLastValue(kosmos.authenticationInteractor.authenticationMethod)
+ val isUnlocked by
+ collectLastValue(
+ kosmos.deviceUnlockedInteractor.deviceUnlockStatus.map { it.isUnlocked }
+ )
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(sceneInteractor.currentOverlays)
+ val isAwake by collectLastValue(powerInteractor.isAwake)
+ prepareState(
+ isDeviceUnlocked = false,
+ initialSceneKey = Scenes.Lockscreen,
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ startsAwake = true,
+ )
+ underTest.start()
+ assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pin)
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(currentOverlays).doesNotContain(Overlays.Bouncer)
+ assertThat(isAwake).isTrue()
+
+ sceneInteractor.showOverlay(Overlays.Bouncer, "")
+ assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pin)
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(currentOverlays).contains(Overlays.Bouncer)
+ assertThat(isAwake).isTrue()
+
+ powerInteractor.setAsleepForTest()
+ assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pin)
+ assertThat(isUnlocked).isFalse()
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(currentOverlays).doesNotContain(Overlays.Bouncer)
+ assertThat(isAwake).isFalse()
+ }
+
+ @Test
+ fun hidesBouncer_whenAuthMethodChangesToNonSecure() =
+ testScope.runTest {
+ val authMethod by collectLastValue(kosmos.authenticationInteractor.authenticationMethod)
+ val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene)
+ val currentOverlays by collectLastValue(kosmos.sceneInteractor.currentOverlays)
+ prepareState(
+ authenticationMethod = AuthenticationMethodModel.Password,
+ initialSceneKey = Scenes.Lockscreen,
+ )
+ underTest.start()
+ assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Password)
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(currentOverlays).doesNotContain(Overlays.Bouncer)
+
+ sceneInteractor.showOverlay(Overlays.Bouncer, "")
+ assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Password)
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(currentOverlays).contains(Overlays.Bouncer)
+
+ kosmos.fakeAuthenticationRepository.setAuthenticationMethod(
+ AuthenticationMethodModel.None
+ )
+ runCurrent()
+
+ assertThat(authMethod).isEqualTo(AuthenticationMethodModel.None)
+ assertThat(currentScene).isEqualTo(Scenes.Lockscreen)
+ assertThat(currentOverlays).doesNotContain(Overlays.Bouncer)
+ }
+
+ @Test
fun replacesLockscreenSceneOnBackStack_whenFaceUnlocked_fromShade_noAlternateBouncer() =
testScope.runTest {
val transitionState =
@@ -2898,7 +2970,10 @@ class SceneContainerStartableTest : SysuiTestCase() {
sceneInteractor.changeScene(it, "prepareState, initialSceneKey isn't null")
}
for (overlay in initialOverlays) {
- sceneInteractor.showOverlay(overlay, "prepareState, initialOverlays isn't empty")
+ sceneInteractor.instantlyShowOverlay(
+ overlay,
+ "prepareState, initialOverlays isn't empty",
+ )
}
if (startsAwake) {
powerInteractor.setAwakeForTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
index b376558371f3..0289c58f6e93 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt
@@ -34,6 +34,7 @@ import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Overlays
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.domain.interactor.disableDualShade
import com.android.systemui.shade.domain.interactor.enableDualShade
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.CommandQueue
@@ -214,6 +215,21 @@ class ShadeControllerSceneImplTest : SysuiTestCase() {
assertThat(currentOverlays).isEmpty()
}
+ @Test
+ fun instantCollapseShade_singleShade_doesntSwitchToShadeScene() =
+ testScope.runTest {
+ kosmos.disableDualShade()
+ runCurrent()
+ val currentScene by collectLastValue(sceneInteractor.currentScene)
+ val homeScene = currentScene
+ sceneInteractor.changeScene(Scenes.QuickSettings, "")
+ assertThat(currentScene).isEqualTo(Scenes.QuickSettings)
+
+ underTest.instantCollapseShade()
+
+ assertThat(currentScene).isEqualTo(homeScene)
+ }
+
private fun setScene(key: SceneKey) {
sceneInteractor.changeScene(key, "test")
sceneInteractor.setTransitionState(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 039a32ba9127..b4c6b33463b0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -48,6 +48,7 @@ import com.android.wm.shell.appzoomout.AppZoomOut
import com.google.common.truth.Truth.assertThat
import java.util.Optional
import java.util.function.Consumer
+import kotlinx.coroutines.flow.MutableStateFlow
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@@ -120,6 +121,8 @@ class NotificationShadeDepthControllerTest : SysuiTestCase() {
`when`(blurUtils.supportsBlursOnWindows()).thenReturn(true)
`when`(blurUtils.maxBlurRadius).thenReturn(maxBlur.toFloat())
`when`(blurUtils.maxBlurRadius).thenReturn(maxBlur.toFloat())
+ `when`(windowRootViewBlurInteractor.isBlurCurrentlySupported)
+ .thenReturn(MutableStateFlow(true))
notificationShadeDepthController =
NotificationShadeDepthController(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt
index 52f68bf4d729..2bb17e110974 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapterTest.kt
@@ -37,7 +37,10 @@ import org.junit.runner.RunWith
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper
+@EnableFlags(NotificationBundleUi.FLAG_NAME)
class BundleEntryAdapterTest : SysuiTestCase() {
+ private lateinit var entry: BundleEntry
+
private val kosmos = testKosmos()
private lateinit var underTest: BundleEntryAdapter
@@ -46,120 +49,107 @@ class BundleEntryAdapterTest : SysuiTestCase() {
@Before
fun setUp() {
- underTest = factory.create(BundleEntry("key")) as BundleEntryAdapter
+ entry = BundleEntry("key")
+ underTest = factory.create(entry) as BundleEntryAdapter
+ }
+
+ @Test
+ fun getBackingHashCode() {
+ assertThat(underTest.backingHashCode).isEqualTo(entry.hashCode())
}
@Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getParent_adapter() {
assertThat(underTest.parent).isEqualTo(GroupEntry.ROOT_ENTRY)
}
@Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isTopLevelEntry_adapter() {
assertThat(underTest.isTopLevelEntry).isTrue()
}
@Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getRow_adapter() {
assertThat(underTest.row).isNull()
}
@Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isGroupRoot_adapter() {
assertThat(underTest.isGroupRoot).isTrue()
}
@Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getKey_adapter() {
assertThat(underTest.key).isEqualTo("key")
}
@Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isClearable_adapter() {
assertThat(underTest.isClearable).isTrue()
}
@Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getSummarization_adapter() {
assertThat(underTest.summarization).isNull()
}
@Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getContrastedColor_adapter() {
assertThat(underTest.getContrastedColor(context, false, Color.WHITE))
.isEqualTo(Notification.COLOR_DEFAULT)
}
@Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun canPeek_adapter() {
assertThat(underTest.canPeek()).isFalse()
}
@Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getWhen_adapter() {
assertThat(underTest.`when`).isEqualTo(0)
}
@Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isColorized() {
assertThat(underTest.isColorized).isFalse()
}
@Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getSbn() {
assertThat(underTest.sbn).isNull()
}
@Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun canDragAndDrop() {
assertThat(underTest.canDragAndDrop()).isFalse()
}
@Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isBubble() {
assertThat(underTest.isBubble).isFalse()
}
@Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getStyle() {
assertThat(underTest.style).isNull()
}
@Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getSectionBucket() {
assertThat(underTest.sectionBucket).isEqualTo(underTest.entry.bucket)
}
@Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun isAmbient() {
assertThat(underTest.isAmbient).isFalse()
}
@Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun canShowFullScreen() {
assertThat(underTest.isFullScreenCapable()).isFalse()
}
@Test
- @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getPeopleNotificationType() {
assertThat(underTest.getPeopleNotificationType()).isEqualTo(TYPE_NON_PERSON)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 4c18025c4cb7..2869979a230f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -1804,7 +1804,8 @@ public class NotifCollectionTest extends SysuiTestCase {
}
private static EntryWithDismissStats entryWithDefaultStats(NotificationEntry entry) {
- return new EntryWithDismissStats(entry, defaultStats(entry));
+ return new EntryWithDismissStats(
+ entry, defaultStats(entry), entry.getKey(), entry.hashCode());
}
private CollectionEvent postNotif(NotificationEntryBuilder builder) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt
index 02c6a484bd43..25b5d68cfbfa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapterTest.kt
@@ -64,6 +64,17 @@ class NotificationEntryAdapterTest : SysuiTestCase() {
@Test
@EnableFlags(NotificationBundleUi.FLAG_NAME)
+ fun getBackingHashCode() {
+ val entry =
+ NotificationEntryBuilder()
+ .build()
+
+ underTest = factory.create(entry) as NotificationEntryAdapter
+ assertThat(underTest.backingHashCode).isEqualTo(entry.hashCode())
+ }
+
+ @Test
+ @EnableFlags(NotificationBundleUi.FLAG_NAME)
fun getParent_adapter() {
val ge = GroupEntryBuilder().build()
val notification: Notification =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index bfd700dcc302..52996ee1e369 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -309,24 +309,6 @@ public class NotificationEntryTest extends SysuiTestCase {
}
@Test
- @EnableFlags(PromotedNotificationUi.FLAG_NAME)
- @DisableFlags(StatusBarNotifChips.FLAG_NAME)
- public void isPromotedOngoing_uiFlagOnAndNotifHasFlag_true() {
- mEntry.getSbn().getNotification().flags |= FLAG_PROMOTED_ONGOING;
-
- assertTrue(mEntry.isPromotedOngoing());
- }
-
- @Test
- @EnableFlags(StatusBarNotifChips.FLAG_NAME)
- @DisableFlags(PromotedNotificationUi.FLAG_NAME)
- public void isPromotedOngoing_statusBarNotifChipsFlagOnAndNotifHasFlag_true() {
- mEntry.getSbn().getNotification().flags |= FLAG_PROMOTED_ONGOING;
-
- assertTrue(mEntry.isPromotedOngoing());
- }
-
- @Test
public void testIsNotificationVisibilityPrivate_true() {
assertTrue(mEntry.isNotificationVisibilityPrivate());
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
index f9405af3f85d..340ce673e01e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator;
import static android.app.NotificationChannel.SYSTEM_RESERVED_IDS;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
@@ -36,7 +37,6 @@ import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.NotificationChannel;
-import android.app.NotificationManager;
import android.platform.test.annotations.EnableFlags;
import androidx.annotation.Nullable;
@@ -265,18 +265,35 @@ public class RankingCoordinatorTest extends SysuiTestCase {
}
@Test
- public void testIncludeInSectionSilent() {
- // GIVEN the entry isn't high priority
+ public void testSilentSectioner_accepts_highPriorityFalse_ambientFalse() {
when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false);
setRankingAmbient(false);
+ assertOnlyInSection(mEntry, mSilentSectioner);
+ }
+
+ @Test
+ public void testSilentSectioner_rejects_highPriorityFalse_ambientTrue() {
+ when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false);
+ setRankingAmbient(true);
+ assertFalse(mSilentSectioner.isInSection(mEntry));
+ }
+
+ @Test
+ public void testSilentSectioner_rejects_highPriorityTrue_ambientFalse() {
+ when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(true);
+ setRankingAmbient(false);
+ assertFalse(mSilentSectioner.isInSection(mEntry));
+ }
- // THEN entry is in the silent section
- assertFalse(mAlertingSectioner.isInSection(mEntry));
- assertTrue(mSilentSectioner.isInSection(mEntry));
+ @Test
+ public void testSilentSectioner_rejects_highPriorityTrue_ambientTrue() {
+ when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(true);
+ setRankingAmbient(true);
+ assertFalse(mSilentSectioner.isInSection(mEntry));
}
@Test
- public void testSilentSectioner_acceptsBundle() {
+ public void testSilentSectioner_accepts_bundle() {
BundleEntry bundleEntry = new BundleEntry("testBundleKey");
assertTrue(mSilentSectioner.isInSection(bundleEntry));
}
@@ -291,14 +308,7 @@ public class RankingCoordinatorTest extends SysuiTestCase {
public void testMinSection() {
when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false);
setRankingAmbient(true);
- assertInSection(mEntry, mMinimizedSectioner);
- }
-
- @Test
- public void testSilentSection() {
- when(mHighPriorityProvider.isHighPriority(mEntry)).thenReturn(false);
- setRankingAmbient(false);
- assertInSection(mEntry, mSilentSectioner);
+ assertOnlyInSection(mEntry, mMinimizedSectioner);
}
@Test
@@ -344,7 +354,8 @@ public class RankingCoordinatorTest extends SysuiTestCase {
@Test
public void testAlertingSectioner_rejectsBundle() {
for (String id : SYSTEM_RESERVED_IDS) {
- assertFalse(mAlertingSectioner.isInSection(makeClassifiedNotifEntry(id)));
+ assertFalse(
+ mAlertingSectioner.isInSection(makeClassifiedNotifEntry(id, IMPORTANCE_LOW)));
}
}
@@ -369,7 +380,7 @@ public class RankingCoordinatorTest extends SysuiTestCase {
reset(mInvalidationListener);
}
- private void assertInSection(NotificationEntry entry, NotifSectioner section) {
+ private void assertOnlyInSection(NotificationEntry entry, NotifSectioner section) {
for (NotifSectioner current: mSections) {
if (current == section) {
assertTrue(current.isInSection(entry));
@@ -396,16 +407,17 @@ public class RankingCoordinatorTest extends SysuiTestCase {
private void setRankingAmbient(boolean ambient) {
mEntry.setRanking(new RankingBuilder(mEntry.getRanking())
.setImportance(ambient
- ? NotificationManager.IMPORTANCE_MIN
+ ? IMPORTANCE_MIN
: IMPORTANCE_DEFAULT)
.build());
assertEquals(ambient, mEntry.getRanking().isAmbient());
}
- private NotificationEntry makeClassifiedNotifEntry(String channelId) {
- NotificationChannel channel = new NotificationChannel(channelId, channelId, IMPORTANCE_LOW);
+ private NotificationEntry makeClassifiedNotifEntry(String channelId, int importance) {
+ NotificationChannel channel = new NotificationChannel(channelId, channelId, importance);
return new NotificationEntryBuilder()
- .updateRanking((rankingBuilder -> rankingBuilder.setChannel(channel)))
+ .updateRanking((rankingBuilder ->
+ rankingBuilder.setChannel(channel).setImportance(importance)))
.build();
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
index 8560b66d961f..5b0e4e139d4e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/headsup/HeadsUpManagerImplTest.kt
@@ -749,20 +749,6 @@ class HeadsUpManagerImplTest(flags: FlagsParameterization) : SysuiTestCase() {
assertThat(getIsSticky_promotedAndExpanded()).isFalse()
}
- @Test
- @EnableFlags(PromotedNotificationUi.FLAG_NAME)
- @DisableFlags(StatusBarNotifChips.FLAG_NAME)
- fun testIsSticky_promotedAndExpanded_promotedUiFlagOn_false() {
- assertThat(getIsSticky_promotedAndExpanded()).isFalse()
- }
-
- @Test
- @EnableFlags(StatusBarNotifChips.FLAG_NAME)
- @DisableFlags(PromotedNotificationUi.FLAG_NAME)
- fun testIsSticky_promotedAndExpanded_notifChipsFlagOn_false() {
- assertThat(getIsSticky_promotedAndExpanded()).isFalse()
- }
-
private fun getIsSticky_promotedAndExpanded(): Boolean {
val notif = Notification.Builder(mContext, "").setSmallIcon(R.drawable.ic_person).build()
notif.flags = FLAG_PROMOTED_ONGOING
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
index cc016b9768b7..df77b5ad46e8 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorImplTest.kt
@@ -33,6 +33,8 @@ 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.kosmos.Kosmos
+import com.android.systemui.kosmos.runTest
import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE
import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC
import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips
@@ -58,132 +60,122 @@ import org.junit.runner.RunWith
class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
private val kosmos = testKosmos().apply { systemClock = fakeSystemClock }
- private val underTest = kosmos.promotedNotificationContentExtractor
- private val systemClock = kosmos.fakeSystemClock
- private val rowImageInflater =
- RowImageInflater.newInstance(previousIndex = null, reinflating = false)
- private val imageModelProvider by lazy { rowImageInflater.useForContentModel() }
+ private val Kosmos.underTest by Kosmos.Fixture { promotedNotificationContentExtractor }
+ private val Kosmos.rowImageInflater by
+ Kosmos.Fixture { RowImageInflater.newInstance(previousIndex = null, reinflating = false) }
+ private val Kosmos.imageModelProvider by
+ Kosmos.Fixture { rowImageInflater.useForContentModel() }
@Test
@DisableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun shouldNotExtract_bothFlagsDisabled() {
- val notif = createEntry()
- val content = extractContent(notif)
- assertThat(content).isNull()
- }
-
- @Test
- @EnableFlags(PromotedNotificationUi.FLAG_NAME)
- @DisableFlags(StatusBarNotifChips.FLAG_NAME)
- fun shouldExtract_promotedNotificationUiFlagEnabled() {
- val entry = createEntry()
- val content = extractContent(entry)
- assertThat(content).isNotNull()
- }
-
- @Test
- @EnableFlags(StatusBarNotifChips.FLAG_NAME)
- @DisableFlags(PromotedNotificationUi.FLAG_NAME)
- fun shouldExtract_statusBarNotifChipsFlagEnabled() {
- val entry = createEntry()
- val content = extractContent(entry)
- assertThat(content).isNotNull()
- }
+ fun shouldNotExtract_bothFlagsDisabled() =
+ kosmos.runTest {
+ val notif = createEntry()
+ val content = extractContent(notif)
+ assertThat(content).isNull()
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun shouldExtract_bothFlagsEnabled() {
- val entry = createEntry()
- val content = extractContent(entry)
- assertThat(content).isNotNull()
- }
+ fun shouldExtract_bothFlagsEnabled() =
+ kosmos.runTest {
+ val entry = createEntry()
+ val content = extractContent(entry)
+ assertThat(content).isNotNull()
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun shouldNotExtract_becauseNotPromoted() {
- val entry = createEntry(promoted = false)
- val content = extractContent(entry)
- assertThat(content).isNull()
- }
+ fun shouldNotExtract_becauseNotPromoted() =
+ kosmos.runTest {
+ val entry = createEntry(promoted = false)
+ val content = extractContent(entry)
+ assertThat(content).isNull()
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractsContent_commonFields() {
- val entry = createEntry {
- setSubText(TEST_SUB_TEXT)
- setContentTitle(TEST_CONTENT_TITLE)
- setContentText(TEST_CONTENT_TEXT)
- }
+ fun extractsContent_commonFields() =
+ kosmos.runTest {
+ val entry = createEntry {
+ setSubText(TEST_SUB_TEXT)
+ setContentTitle(TEST_CONTENT_TITLE)
+ setContentText(TEST_CONTENT_TEXT)
+ }
- val content = requireContent(entry)
+ val content = requireContent(entry)
- content.privateVersion.apply {
- assertThat(subText).isEqualTo(TEST_SUB_TEXT)
- assertThat(title).isEqualTo(TEST_CONTENT_TITLE)
- assertThat(text).isEqualTo(TEST_CONTENT_TEXT)
- }
+ content.privateVersion.apply {
+ assertThat(subText).isEqualTo(TEST_SUB_TEXT)
+ assertThat(title).isEqualTo(TEST_CONTENT_TITLE)
+ assertThat(text).isEqualTo(TEST_CONTENT_TEXT)
+ }
- content.publicVersion.apply {
- assertThat(subText).isNull()
- assertThat(title).isNull()
- assertThat(text).isNull()
+ content.publicVersion.apply {
+ assertThat(subText).isNull()
+ assertThat(title).isNull()
+ assertThat(text).isNull()
+ }
}
- }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractsContent_commonFields_noRedaction() {
- val entry = createEntry {
- setSubText(TEST_SUB_TEXT)
- setContentTitle(TEST_CONTENT_TITLE)
- setContentText(TEST_CONTENT_TEXT)
- }
+ fun extractsContent_commonFields_noRedaction() =
+ kosmos.runTest {
+ val entry = createEntry {
+ setSubText(TEST_SUB_TEXT)
+ setContentTitle(TEST_CONTENT_TITLE)
+ setContentText(TEST_CONTENT_TEXT)
+ }
- val content = requireContent(entry, redactionType = REDACTION_TYPE_NONE)
+ val content = requireContent(entry, redactionType = REDACTION_TYPE_NONE)
- content.privateVersion.apply {
- assertThat(subText).isEqualTo(TEST_SUB_TEXT)
- assertThat(title).isEqualTo(TEST_CONTENT_TITLE)
- assertThat(text).isEqualTo(TEST_CONTENT_TEXT)
- }
+ content.privateVersion.apply {
+ assertThat(subText).isEqualTo(TEST_SUB_TEXT)
+ assertThat(title).isEqualTo(TEST_CONTENT_TITLE)
+ assertThat(text).isEqualTo(TEST_CONTENT_TEXT)
+ }
- content.publicVersion.apply {
- assertThat(subText).isEqualTo(TEST_SUB_TEXT)
- assertThat(title).isEqualTo(TEST_CONTENT_TITLE)
- assertThat(text).isEqualTo(TEST_CONTENT_TEXT)
+ content.publicVersion.apply {
+ assertThat(subText).isEqualTo(TEST_SUB_TEXT)
+ assertThat(title).isEqualTo(TEST_CONTENT_TITLE)
+ assertThat(text).isEqualTo(TEST_CONTENT_TEXT)
+ }
}
- }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractContent_wasPromotedAutomatically_false() {
- val entry = createEntry { extras.putBoolean(EXTRA_WAS_AUTOMATICALLY_PROMOTED, false) }
+ fun extractContent_wasPromotedAutomatically_false() =
+ kosmos.runTest {
+ val entry = createEntry { extras.putBoolean(EXTRA_WAS_AUTOMATICALLY_PROMOTED, false) }
- val content = requireContent(entry).privateVersion
+ val content = requireContent(entry).privateVersion
- assertThat(content.wasPromotedAutomatically).isFalse()
- }
+ assertThat(content.wasPromotedAutomatically).isFalse()
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractContent_wasPromotedAutomatically_true() {
- val entry = createEntry { extras.putBoolean(EXTRA_WAS_AUTOMATICALLY_PROMOTED, true) }
+ fun extractContent_wasPromotedAutomatically_true() =
+ kosmos.runTest {
+ val entry = createEntry { extras.putBoolean(EXTRA_WAS_AUTOMATICALLY_PROMOTED, true) }
- val content = requireContent(entry).privateVersion
+ val content = requireContent(entry).privateVersion
- assertThat(content.wasPromotedAutomatically).isTrue()
- }
+ assertThat(content.wasPromotedAutomatically).isTrue()
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
@DisableFlags(android.app.Flags.FLAG_API_RICH_ONGOING)
- fun extractContent_apiFlagOff_shortCriticalTextNotExtracted() {
- val entry = createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) }
+ fun extractContent_apiFlagOff_shortCriticalTextNotExtracted() =
+ kosmos.runTest {
+ val entry = createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) }
- val content = requireContent(entry).privateVersion
+ val content = requireContent(entry).privateVersion
- assertThat(content.text).isNull()
- }
+ assertThat(content.text).isNull()
+ }
@Test
@EnableFlags(
@@ -191,13 +183,14 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
StatusBarNotifChips.FLAG_NAME,
android.app.Flags.FLAG_API_RICH_ONGOING,
)
- fun extractContent_apiFlagOn_shortCriticalTextExtracted() {
- val entry = createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) }
+ fun extractContent_apiFlagOn_shortCriticalTextExtracted() =
+ kosmos.runTest {
+ val entry = createEntry { setShortCriticalText(TEST_SHORT_CRITICAL_TEXT) }
- val content = requireContent(entry).privateVersion
+ val content = requireContent(entry).privateVersion
- assertThat(content.shortCriticalText).isEqualTo(TEST_SHORT_CRITICAL_TEXT)
- }
+ assertThat(content.shortCriticalText).isEqualTo(TEST_SHORT_CRITICAL_TEXT)
+ }
@Test
@EnableFlags(
@@ -205,165 +198,188 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
StatusBarNotifChips.FLAG_NAME,
android.app.Flags.FLAG_API_RICH_ONGOING,
)
- fun extractContent_noShortCriticalTextSet_textIsNull() {
- val entry = createEntry { setShortCriticalText(null) }
+ fun extractContent_noShortCriticalTextSet_textIsNull() =
+ kosmos.runTest {
+ val entry = createEntry { setShortCriticalText(null) }
- val content = requireContent(entry).privateVersion
+ val content = requireContent(entry).privateVersion
- assertThat(content.shortCriticalText).isNull()
- }
+ assertThat(content.shortCriticalText).isNull()
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractTime_none() {
- assertExtractedTime(hasTime = false, hasChronometer = false, expected = ExpectedTime.Null)
- }
+ fun extractTime_none() =
+ kosmos.runTest {
+ assertExtractedTime(
+ hasTime = false,
+ hasChronometer = false,
+ expected = ExpectedTime.Null,
+ )
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractTime_basicTimeZero() {
- assertExtractedTime(
- hasTime = true,
- hasChronometer = false,
- provided = ProvidedTime.Value(0L),
- expected = ExpectedTime.Time,
- )
- }
+ fun extractTime_basicTimeZero() =
+ kosmos.runTest {
+ assertExtractedTime(
+ hasTime = true,
+ hasChronometer = false,
+ provided = ProvidedTime.Value(0L),
+ expected = ExpectedTime.Time,
+ )
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractTime_basicTimeNow() {
- assertExtractedTime(
- hasTime = true,
- hasChronometer = false,
- provided = ProvidedTime.Offset(Duration.ZERO),
- expected = ExpectedTime.Time,
- )
- }
+ fun extractTime_basicTimeNow() =
+ kosmos.runTest {
+ assertExtractedTime(
+ hasTime = true,
+ hasChronometer = false,
+ provided = ProvidedTime.Offset(Duration.ZERO),
+ expected = ExpectedTime.Time,
+ )
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractTime_basicTimePast() {
- assertExtractedTime(
- hasTime = true,
- hasChronometer = false,
- provided = ProvidedTime.Offset((-5).minutes),
- expected = ExpectedTime.Time,
- )
- }
+ fun extractTime_basicTimePast() =
+ kosmos.runTest {
+ assertExtractedTime(
+ hasTime = true,
+ hasChronometer = false,
+ provided = ProvidedTime.Offset((-5).minutes),
+ expected = ExpectedTime.Time,
+ )
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractTime_basicTimeFuture() {
- assertExtractedTime(
- hasTime = true,
- hasChronometer = false,
- provided = ProvidedTime.Offset(5.minutes),
- expected = ExpectedTime.Time,
- )
- }
+ fun extractTime_basicTimeFuture() =
+ kosmos.runTest {
+ assertExtractedTime(
+ hasTime = true,
+ hasChronometer = false,
+ provided = ProvidedTime.Offset(5.minutes),
+ expected = ExpectedTime.Time,
+ )
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractTime_countUpZero() {
- assertExtractedTime(
- hasTime = false,
- hasChronometer = true,
- isCountDown = false,
- provided = ProvidedTime.Value(0L),
- expected = ExpectedTime.CountUp,
- )
- }
+ fun extractTime_countUpZero() =
+ kosmos.runTest {
+ assertExtractedTime(
+ hasTime = false,
+ hasChronometer = true,
+ isCountDown = false,
+ provided = ProvidedTime.Value(0L),
+ expected = ExpectedTime.CountUp,
+ )
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractTime_countUpNow() {
- assertExtractedTime(
- hasTime = false,
- hasChronometer = true,
- isCountDown = false,
- provided = ProvidedTime.Offset(Duration.ZERO),
- expected = ExpectedTime.CountUp,
- )
- }
+ fun extractTime_countUpNow() =
+ kosmos.runTest {
+ assertExtractedTime(
+ hasTime = false,
+ hasChronometer = true,
+ isCountDown = false,
+ provided = ProvidedTime.Offset(Duration.ZERO),
+ expected = ExpectedTime.CountUp,
+ )
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractTime_countUpPast() {
- assertExtractedTime(
- hasTime = false,
- hasChronometer = true,
- isCountDown = false,
- provided = ProvidedTime.Offset((-5).minutes),
- expected = ExpectedTime.CountUp,
- )
- }
+ fun extractTime_countUpPast() =
+ kosmos.runTest {
+ assertExtractedTime(
+ hasTime = false,
+ hasChronometer = true,
+ isCountDown = false,
+ provided = ProvidedTime.Offset((-5).minutes),
+ expected = ExpectedTime.CountUp,
+ )
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractTime_countUpFuture() {
- assertExtractedTime(
- hasTime = false,
- hasChronometer = true,
- isCountDown = false,
- provided = ProvidedTime.Offset(5.minutes),
- expected = ExpectedTime.CountUp,
- )
- }
+ fun extractTime_countUpFuture() =
+ kosmos.runTest {
+ assertExtractedTime(
+ hasTime = false,
+ hasChronometer = true,
+ isCountDown = false,
+ provided = ProvidedTime.Offset(5.minutes),
+ expected = ExpectedTime.CountUp,
+ )
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractTime_countDownZero() {
- assertExtractedTime(
- hasTime = false,
- hasChronometer = true,
- isCountDown = true,
- provided = ProvidedTime.Value(0L),
- expected = ExpectedTime.CountDown,
- )
- }
+ fun extractTime_countDownZero() =
+ kosmos.runTest {
+ assertExtractedTime(
+ hasTime = false,
+ hasChronometer = true,
+ isCountDown = true,
+ provided = ProvidedTime.Value(0L),
+ expected = ExpectedTime.CountDown,
+ )
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractTime_countDownNow() {
- assertExtractedTime(
- hasTime = false,
- hasChronometer = true,
- isCountDown = true,
- provided = ProvidedTime.Offset(Duration.ZERO),
- expected = ExpectedTime.CountDown,
- )
- }
+ fun extractTime_countDownNow() =
+ kosmos.runTest {
+ assertExtractedTime(
+ hasTime = false,
+ hasChronometer = true,
+ isCountDown = true,
+ provided = ProvidedTime.Offset(Duration.ZERO),
+ expected = ExpectedTime.CountDown,
+ )
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractTime_countDownPast() {
- assertExtractedTime(
- hasTime = false,
- hasChronometer = true,
- isCountDown = true,
- provided = ProvidedTime.Offset((-5).minutes),
- expected = ExpectedTime.CountDown,
- )
- }
+ fun extractTime_countDownPast() =
+ kosmos.runTest {
+ assertExtractedTime(
+ hasTime = false,
+ hasChronometer = true,
+ isCountDown = true,
+ provided = ProvidedTime.Offset((-5).minutes),
+ expected = ExpectedTime.CountDown,
+ )
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractTime_countDownFuture() {
- assertExtractedTime(
- hasTime = false,
- hasChronometer = true,
- isCountDown = true,
- provided = ProvidedTime.Offset(5.minutes),
- expected = ExpectedTime.CountDown,
- )
- }
+ fun extractTime_countDownFuture() =
+ kosmos.runTest {
+ assertExtractedTime(
+ hasTime = false,
+ hasChronometer = true,
+ isCountDown = true,
+ provided = ProvidedTime.Offset(5.minutes),
+ expected = ExpectedTime.CountDown,
+ )
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractTime_prefersChronometerToWhen() {
- assertExtractedTime(hasTime = true, hasChronometer = true, expected = ExpectedTime.CountUp)
- }
+ fun extractTime_prefersChronometerToWhen() =
+ kosmos.runTest {
+ assertExtractedTime(
+ hasTime = true,
+ hasChronometer = true,
+ expected = ExpectedTime.CountUp,
+ )
+ }
private sealed class ProvidedTime {
data class Value(val value: Long) : ProvidedTime()
@@ -378,7 +394,7 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
CountDown,
}
- private fun assertExtractedTime(
+ private fun Kosmos.assertExtractedTime(
hasTime: Boolean = false,
hasChronometer: Boolean = false,
isCountDown: Boolean = false,
@@ -387,8 +403,8 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
) {
// Set the two timebases to different (arbitrary) numbers, so we can verify whether the
// extractor is doing the timebase adjustment correctly.
- systemClock.setCurrentTimeMillis(1_739_570_992_579L)
- systemClock.setElapsedRealtime(1_380_967_080L)
+ fakeSystemClock.setCurrentTimeMillis(1_739_570_992_579L)
+ fakeSystemClock.setElapsedRealtime(1_380_967_080L)
val providedCurrentTime =
when (provided) {
@@ -437,122 +453,130 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractContent_fromBaseStyle() {
- val entry = createEntry { setStyle(null) }
+ fun extractContent_fromBaseStyle() =
+ kosmos.runTest {
+ val entry = createEntry { setStyle(null) }
- val content = requireContent(entry)
+ val content = requireContent(entry)
- assertThat(content.privateVersion.style).isEqualTo(Style.Base)
- assertThat(content.publicVersion.style).isEqualTo(Style.Base)
- }
+ assertThat(content.privateVersion.style).isEqualTo(Style.Base)
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractContent_fromBigPictureStyle() {
- val entry = createEntry { setStyle(BigPictureStyle()) }
+ fun extractContent_fromBigPictureStyle() =
+ kosmos.runTest {
+ val entry = createEntry { setStyle(BigPictureStyle()) }
- val content = requireContent(entry)
+ val content = requireContent(entry)
- assertThat(content.privateVersion.style).isEqualTo(Style.BigPicture)
- assertThat(content.publicVersion.style).isEqualTo(Style.Base)
- }
+ assertThat(content.privateVersion.style).isEqualTo(Style.BigPicture)
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractContent_fromBigTextStyle() {
- val entry = createEntry {
- setContentTitle(TEST_CONTENT_TITLE)
- setContentText(TEST_CONTENT_TEXT)
- setStyle(
- BigTextStyle()
- .bigText(TEST_BIG_TEXT)
- .setBigContentTitle(TEST_BIG_CONTENT_TITLE)
- .setSummaryText(TEST_SUMMARY_TEXT)
- )
- }
+ fun extractContent_fromBigTextStyle() =
+ kosmos.runTest {
+ val entry = createEntry {
+ setContentTitle(TEST_CONTENT_TITLE)
+ setContentText(TEST_CONTENT_TEXT)
+ setStyle(
+ BigTextStyle()
+ .bigText(TEST_BIG_TEXT)
+ .setBigContentTitle(TEST_BIG_CONTENT_TITLE)
+ .setSummaryText(TEST_SUMMARY_TEXT)
+ )
+ }
- val content = requireContent(entry)
+ val content = requireContent(entry)
- assertThat(content.privateVersion.style).isEqualTo(Style.BigText)
- assertThat(content.privateVersion.title).isEqualTo(TEST_BIG_CONTENT_TITLE)
- assertThat(content.privateVersion.text).isEqualTo(TEST_BIG_TEXT)
+ assertThat(content.privateVersion.style).isEqualTo(Style.BigText)
+ assertThat(content.privateVersion.title).isEqualTo(TEST_BIG_CONTENT_TITLE)
+ assertThat(content.privateVersion.text).isEqualTo(TEST_BIG_TEXT)
- assertThat(content.publicVersion.style).isEqualTo(Style.Base)
- assertThat(content.publicVersion.title).isNull()
- assertThat(content.publicVersion.text).isNull()
- }
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
+ assertThat(content.publicVersion.title).isNull()
+ assertThat(content.publicVersion.text).isNull()
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractContent_fromBigTextStyle_fallbackToContentTitle() {
- val entry = createEntry {
- setContentTitle(TEST_CONTENT_TITLE)
- setContentText(TEST_CONTENT_TEXT)
- setStyle(
- BigTextStyle()
- .bigText(TEST_BIG_TEXT)
- // bigContentTitle unset
- .setSummaryText(TEST_SUMMARY_TEXT)
- )
- }
+ fun extractContent_fromBigTextStyle_fallbackToContentTitle() =
+ kosmos.runTest {
+ val entry = createEntry {
+ setContentTitle(TEST_CONTENT_TITLE)
+ setContentText(TEST_CONTENT_TEXT)
+ setStyle(
+ BigTextStyle()
+ .bigText(TEST_BIG_TEXT)
+ // bigContentTitle unset
+ .setSummaryText(TEST_SUMMARY_TEXT)
+ )
+ }
- val content = requireContent(entry)
+ val content = requireContent(entry)
- assertThat(content.privateVersion.style).isEqualTo(Style.BigText)
- assertThat(content.privateVersion.title).isEqualTo(TEST_CONTENT_TITLE)
- assertThat(content.privateVersion.text).isEqualTo(TEST_BIG_TEXT)
+ assertThat(content.privateVersion.style).isEqualTo(Style.BigText)
+ assertThat(content.privateVersion.title).isEqualTo(TEST_CONTENT_TITLE)
+ assertThat(content.privateVersion.text).isEqualTo(TEST_BIG_TEXT)
- assertThat(content.publicVersion.style).isEqualTo(Style.Base)
- assertThat(content.publicVersion.title).isNull()
- assertThat(content.publicVersion.text).isNull()
- }
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
+ assertThat(content.publicVersion.title).isNull()
+ assertThat(content.publicVersion.text).isNull()
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractContent_fromBigTextStyle_fallbackToContentText() {
- val entry = createEntry {
- setContentTitle(TEST_CONTENT_TITLE)
- setContentText(TEST_CONTENT_TEXT)
- setStyle(
- BigTextStyle()
- // bigText unset
- .setBigContentTitle(TEST_BIG_CONTENT_TITLE)
- .setSummaryText(TEST_SUMMARY_TEXT)
- )
- }
+ fun extractContent_fromBigTextStyle_fallbackToContentText() =
+ kosmos.runTest {
+ val entry = createEntry {
+ setContentTitle(TEST_CONTENT_TITLE)
+ setContentText(TEST_CONTENT_TEXT)
+ setStyle(
+ BigTextStyle()
+ // bigText unset
+ .setBigContentTitle(TEST_BIG_CONTENT_TITLE)
+ .setSummaryText(TEST_SUMMARY_TEXT)
+ )
+ }
- val content = requireContent(entry)
+ val content = requireContent(entry)
- assertThat(content.privateVersion.style).isEqualTo(Style.BigText)
- assertThat(content.privateVersion.title).isEqualTo(TEST_BIG_CONTENT_TITLE)
- assertThat(content.privateVersion.text).isEqualTo(TEST_CONTENT_TEXT)
+ assertThat(content.privateVersion.style).isEqualTo(Style.BigText)
+ assertThat(content.privateVersion.title).isEqualTo(TEST_BIG_CONTENT_TITLE)
+ assertThat(content.privateVersion.text).isEqualTo(TEST_CONTENT_TEXT)
- assertThat(content.publicVersion.style).isEqualTo(Style.Base)
- assertThat(content.publicVersion.title).isNull()
- assertThat(content.publicVersion.text).isNull()
- }
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
+ assertThat(content.publicVersion.title).isNull()
+ assertThat(content.publicVersion.text).isNull()
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractContent_fromCallStyle() {
- val hangUpIntent =
- PendingIntent.getBroadcast(
- context,
- 0,
- Intent("hangup_action"),
- PendingIntent.FLAG_IMMUTABLE,
- )
- val entry = createEntry { setStyle(CallStyle.forOngoingCall(TEST_PERSON, hangUpIntent)) }
+ fun extractContent_fromCallStyle() =
+ kosmos.runTest {
+ val hangUpIntent =
+ PendingIntent.getBroadcast(
+ context,
+ 0,
+ Intent("hangup_action"),
+ PendingIntent.FLAG_IMMUTABLE,
+ )
+ val entry = createEntry {
+ setStyle(CallStyle.forOngoingCall(TEST_PERSON, hangUpIntent))
+ }
- val content = requireContent(entry)
+ val content = requireContent(entry)
- assertThat(content.privateVersion.style).isEqualTo(Style.Call)
- assertThat(content.privateVersion.title).isEqualTo(TEST_PERSON_NAME)
+ assertThat(content.privateVersion.style).isEqualTo(Style.Call)
+ assertThat(content.privateVersion.title).isEqualTo(TEST_PERSON_NAME)
- assertThat(content.publicVersion.style).isEqualTo(Style.Base)
- assertThat(content.publicVersion.title).isNull()
- assertThat(content.publicVersion.text).isNull()
- }
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
+ assertThat(content.publicVersion.title).isNull()
+ assertThat(content.publicVersion.text).isNull()
+ }
@Test
@EnableFlags(
@@ -560,75 +584,79 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
StatusBarNotifChips.FLAG_NAME,
android.app.Flags.FLAG_API_RICH_ONGOING,
)
- fun extractContent_fromProgressStyle() {
- val entry = createEntry {
- setStyle(ProgressStyle().addProgressSegment(Segment(100)).setProgress(75))
- }
+ fun extractContent_fromProgressStyle() =
+ kosmos.runTest {
+ val entry = createEntry {
+ setStyle(ProgressStyle().addProgressSegment(Segment(100)).setProgress(75))
+ }
- val content = requireContent(entry)
+ val content = requireContent(entry)
- assertThat(content.privateVersion.style).isEqualTo(Style.Progress)
- val newProgress = assertNotNull(content.privateVersion.newProgress)
- assertThat(newProgress.progress).isEqualTo(75)
- assertThat(newProgress.progressMax).isEqualTo(100)
+ assertThat(content.privateVersion.style).isEqualTo(Style.Progress)
+ val newProgress = assertNotNull(content.privateVersion.newProgress)
+ assertThat(newProgress.progress).isEqualTo(75)
+ assertThat(newProgress.progressMax).isEqualTo(100)
- assertThat(content.publicVersion.style).isEqualTo(Style.Base)
- assertThat(content.publicVersion.title).isNull()
- assertThat(content.publicVersion.text).isNull()
- assertThat(content.publicVersion.newProgress).isNull()
- }
+ assertThat(content.publicVersion.style).isEqualTo(Style.Base)
+ assertThat(content.publicVersion.title).isNull()
+ assertThat(content.publicVersion.text).isNull()
+ assertThat(content.publicVersion.newProgress).isNull()
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractContent_fromIneligibleStyle() {
- val entry = createEntry {
- setStyle(MessagingStyle(TEST_PERSON).addMessage("message text", 0L, TEST_PERSON))
- }
+ fun extractContent_fromIneligibleStyle() =
+ kosmos.runTest {
+ val entry = createEntry {
+ setStyle(MessagingStyle(TEST_PERSON).addMessage("message text", 0L, TEST_PERSON))
+ }
- val content = requireContent(entry)
+ val content = requireContent(entry)
- assertThat(content.privateVersion.style).isEqualTo(Style.Ineligible)
+ assertThat(content.privateVersion.style).isEqualTo(Style.Ineligible)
- assertThat(content.publicVersion.style).isEqualTo(Style.Ineligible)
- }
+ assertThat(content.publicVersion.style).isEqualTo(Style.Ineligible)
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractContent_fromOldProgressDeterminate() {
- val entry = createEntry {
- setProgress(TEST_PROGRESS_MAX, TEST_PROGRESS, /* indeterminate= */ false)
- }
+ fun extractContent_fromOldProgressDeterminate() =
+ kosmos.runTest {
+ val entry = createEntry {
+ setProgress(TEST_PROGRESS_MAX, TEST_PROGRESS, /* indeterminate= */ false)
+ }
- val content = requireContent(entry)
+ val content = requireContent(entry)
- val oldProgress = assertNotNull(content.privateVersion.oldProgress)
+ val oldProgress = assertNotNull(content.privateVersion.oldProgress)
- assertThat(oldProgress.progress).isEqualTo(TEST_PROGRESS)
- assertThat(oldProgress.max).isEqualTo(TEST_PROGRESS_MAX)
- assertThat(oldProgress.isIndeterminate).isFalse()
- }
+ assertThat(oldProgress.progress).isEqualTo(TEST_PROGRESS)
+ assertThat(oldProgress.max).isEqualTo(TEST_PROGRESS_MAX)
+ assertThat(oldProgress.isIndeterminate).isFalse()
+ }
@Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
- fun extractContent_fromOldProgressIndeterminate() {
- val entry = createEntry {
- setProgress(TEST_PROGRESS_MAX, TEST_PROGRESS, /* indeterminate= */ true)
- }
+ fun extractContent_fromOldProgressIndeterminate() =
+ kosmos.runTest {
+ val entry = createEntry {
+ setProgress(TEST_PROGRESS_MAX, TEST_PROGRESS, /* indeterminate= */ true)
+ }
- val content = requireContent(entry)
- val oldProgress = assertNotNull(content.privateVersion.oldProgress)
+ val content = requireContent(entry)
+ val oldProgress = assertNotNull(content.privateVersion.oldProgress)
- assertThat(oldProgress.progress).isEqualTo(TEST_PROGRESS)
- assertThat(oldProgress.max).isEqualTo(TEST_PROGRESS_MAX)
- assertThat(oldProgress.isIndeterminate).isTrue()
- }
+ assertThat(oldProgress.progress).isEqualTo(TEST_PROGRESS)
+ assertThat(oldProgress.max).isEqualTo(TEST_PROGRESS_MAX)
+ assertThat(oldProgress.isIndeterminate).isTrue()
+ }
- private fun requireContent(
+ private fun Kosmos.requireContent(
entry: NotificationEntry,
redactionType: Int = REDACTION_TYPE_PUBLIC,
): PromotedNotificationContentModels = assertNotNull(extractContent(entry, redactionType))
- private fun extractContent(
+ private fun Kosmos.extractContent(
entry: NotificationEntry,
redactionType: Int = REDACTION_TYPE_PUBLIC,
): PromotedNotificationContentModels? {
@@ -636,7 +664,7 @@ class PromotedNotificationContentExtractorImplTest : SysuiTestCase() {
return underTest.extractContent(entry, recoveredBuilder, redactionType, imageModelProvider)
}
- private fun createEntry(
+ private fun Kosmos.createEntry(
promoted: Boolean = true,
builderBlock: Notification.Builder.() -> Unit = {},
): NotificationEntry {
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
new file mode 100644
index 000000000000..bad33a402ff7
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationsInteractorTest.kt
@@ -0,0 +1,147 @@
+/*
+ * 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.statusbar.notification.promoted.domain.interactor
+
+import android.app.Notification
+import android.content.applicationContext
+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.dump.dumpManager
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.kosmos.collectLastValue
+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.policy.domain.interactor.sensitiveNotificationProtectionInteractor
+import com.android.systemui.statusbar.policy.mockSensitiveNotificationProtectionController
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.whenever
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableFlags(
+ PromotedNotificationUi.FLAG_NAME,
+ StatusBarNotifChips.FLAG_NAME,
+ StatusBarChipsModernization.FLAG_NAME,
+ StatusBarRootModernization.FLAG_NAME,
+)
+class AODPromotedNotificationsInteractorTest : SysuiTestCase() {
+ private val kosmos = testKosmos().useUnconfinedTestDispatcher()
+
+ private val Kosmos.underTest by Fixture {
+ AODPromotedNotificationInteractor(
+ promotedNotificationsInteractor = promotedNotificationsInteractor,
+ keyguardInteractor = keyguardInteractor,
+ sensitiveNotificationProtectionInteractor = sensitiveNotificationProtectionInteractor,
+ dumpManager = dumpManager,
+ )
+ }
+
+ @Before
+ fun setUp() {
+ kosmos.statusBarNotificationChipsInteractor.start()
+ }
+
+ private fun Kosmos.buildPublicPrivatePromotedOngoing(): NotificationEntry =
+ buildPromotedOngoingEntry {
+ modifyNotification(applicationContext)
+ .setContentTitle("SENSITIVE")
+ .setPublicVersion(
+ Notification.Builder(applicationContext, "channel")
+ .setContentTitle("REDACTED")
+ .build()
+ )
+ }
+
+ @Test
+ fun content_sensitive_unlocked() =
+ kosmos.runTest {
+ // GIVEN a promoted entry
+ val ronEntry = buildPublicPrivatePromotedOngoing()
+
+ setKeyguardLocked(false)
+ setScreenSharingProtectionActive(false)
+
+ renderNotificationListInteractor.setRenderedList(listOf(ronEntry))
+
+ // THEN aod content is sensitive
+ val content by collectLastValue(underTest.content)
+ assertThat(content?.title).isEqualTo("SENSITIVE")
+ }
+
+ @Test
+ fun content_sensitive_locked() =
+ kosmos.runTest {
+ // GIVEN a promoted entry
+ val ronEntry = buildPublicPrivatePromotedOngoing()
+
+ setKeyguardLocked(true)
+ setScreenSharingProtectionActive(false)
+
+ renderNotificationListInteractor.setRenderedList(listOf(ronEntry))
+
+ // THEN aod content is sensitive
+ val content by collectLastValue(underTest.content)
+ assertThat(content).isNotNull()
+ assertThat(content?.title).isNull() // SOON: .isEqualTo("REDACTED")
+ }
+
+ @Test
+ fun content_sensitive_unlocked_screensharing() =
+ kosmos.runTest {
+ // GIVEN a promoted entry
+ val ronEntry = buildPublicPrivatePromotedOngoing()
+
+ setKeyguardLocked(false)
+ setScreenSharingProtectionActive(true)
+
+ renderNotificationListInteractor.setRenderedList(listOf(ronEntry))
+
+ // THEN aod content is sensitive
+ val content by collectLastValue(underTest.content)
+ assertThat(content).isNotNull()
+ assertThat(content?.title).isNull() // SOON: .isEqualTo("REDACTED")
+ }
+
+ private fun Kosmos.setKeyguardLocked(locked: Boolean) {
+ fakeKeyguardRepository.setKeyguardDismissible(!locked)
+ }
+
+ private fun Kosmos.setScreenSharingProtectionActive(active: Boolean) {
+ whenever(mockSensitiveNotificationProtectionController.isSensitiveStateActive)
+ .thenReturn(active)
+ whenever(mockSensitiveNotificationProtectionController.shouldProtectNotification(any()))
+ .thenReturn(active)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index 19b1046f1931..4aa21a68b2e0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -399,35 +399,6 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
}
@Test
- @EnableFlags(PromotedNotificationUi.FLAG_NAME)
- @DisableFlags(StatusBarNotifChips.FLAG_NAME)
- public void testExtractsPromotedContent_whePromotedNotificationUiFlagEnabled()
- throws Exception {
- final PromotedNotificationContentModels content =
- new PromotedNotificationContentBuilder("key").build();
- mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content);
-
- inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
-
- mPromotedNotificationContentExtractor.verifyOneExtractCall();
- assertEquals(content, mRow.getEntry().getPromotedNotificationContentModels());
- }
-
- @Test
- @EnableFlags(StatusBarNotifChips.FLAG_NAME)
- @DisableFlags(PromotedNotificationUi.FLAG_NAME)
- public void testExtractsPromotedContent_whenStatusBarNotifChipsFlagEnabled() throws Exception {
- final PromotedNotificationContentModels content =
- new PromotedNotificationContentBuilder("key").build();
- mPromotedNotificationContentExtractor.resetForEntry(mRow.getEntry(), content);
-
- inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow);
-
- mPromotedNotificationContentExtractor.verifyOneExtractCall();
- assertEquals(content, mRow.getEntry().getPromotedNotificationContentModels());
- }
-
- @Test
@EnableFlags({PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME})
public void testExtractsPromotedContent_whenBothFlagsEnabled() throws Exception {
final PromotedNotificationContentModels content =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
index dcba3e447dda..21b0c9013b5f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderImplTest.kt
@@ -465,32 +465,6 @@ class NotificationRowContentBinderImplTest : SysuiTestCase() {
}
@Test
- @EnableFlags(PromotedNotificationUi.FLAG_NAME)
- @DisableFlags(StatusBarNotifChips.FLAG_NAME)
- fun testExtractsPromotedContent_whenPromotedNotificationUiFlagEnabled() {
- val content = PromotedNotificationContentBuilder("key").build()
- promotedNotificationContentExtractor.resetForEntry(row.entry, content)
-
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
-
- promotedNotificationContentExtractor.verifyOneExtractCall()
- Assert.assertEquals(content, row.entry.promotedNotificationContentModels)
- }
-
- @Test
- @EnableFlags(StatusBarNotifChips.FLAG_NAME)
- @DisableFlags(PromotedNotificationUi.FLAG_NAME)
- fun testExtractsPromotedContent_whenStatusBarNotifChipsFlagEnabled() {
- val content = PromotedNotificationContentBuilder("key").build()
- promotedNotificationContentExtractor.resetForEntry(row.entry, content)
-
- inflateAndWait(notificationInflater, FLAG_CONTENT_VIEW_ALL, row)
-
- promotedNotificationContentExtractor.verifyOneExtractCall()
- Assert.assertEquals(content, row.entry.promotedNotificationContentModels)
- }
-
- @Test
@EnableFlags(PromotedNotificationUi.FLAG_NAME, StatusBarNotifChips.FLAG_NAME)
fun testExtractsPromotedContent_whenBothFlagsEnabled() {
val content = PromotedNotificationContentBuilder("key").build()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 716353945be2..999a78af0c68 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static android.service.dreams.Flags.FLAG_DREAMS_V2;
+
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE;
import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
@@ -38,6 +40,7 @@ import android.hardware.fingerprint.FingerprintManager;
import android.os.Handler;
import android.os.PowerManager;
import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
import android.testing.TestableLooper.RunWithLooper;
import android.testing.TestableResources;
import android.view.ViewRootImpl;
@@ -337,6 +340,38 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
}
@Test
+ public void onBiometricAuthenticated_whenFaceAndDreaming_dontDismissKeyguard() {
+ when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
+ when(mUpdateMonitor.isDreaming()).thenReturn(true);
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ // the value of isStrongBiometric doesn't matter here since we only care about the returned
+ // value of isUnlockingWithBiometricAllowed()
+ mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
+ BiometricSourceType.FACE, true /* isStrongBiometric */);
+
+ verify(mStatusBarKeyguardViewManager, never()).notifyKeyguardAuthenticated(anyBoolean());
+ assertThat(mBiometricUnlockController.getMode())
+ .isEqualTo(BiometricUnlockController.MODE_ONLY_WAKE);
+ }
+
+ @Test
+ @EnableFlags(FLAG_DREAMS_V2)
+ public void onBiometricAuthenticated_whenFaceOnBouncerAndDreaming_dismissKeyguard() {
+ when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
+ when(mUpdateMonitor.isDreaming()).thenReturn(true);
+ when(mUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()).thenReturn(true);
+ // the value of isStrongBiometric doesn't matter here since we only care about the returned
+ // value of isUnlockingWithBiometricAllowed()
+ mBiometricUnlockController.onBiometricAuthenticated(UserHandle.USER_CURRENT,
+ BiometricSourceType.FACE, true /* isStrongBiometric */);
+
+ verify(mStatusBarKeyguardViewManager, never()).notifyKeyguardAuthenticated(anyBoolean());
+ assertThat(mBiometricUnlockController.getMode())
+ .isEqualTo(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM);
+ }
+
+ @Test
public void onBiometricAuthenticated_onLockScreen() {
// GIVEN not dozing
when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
diff --git a/packages/SystemUI/res/drawable/notification_menu_button_background.xml b/packages/SystemUI/res/drawable/notification_menu_button_background.xml
new file mode 100644
index 000000000000..a3014d9ea566
--- /dev/null
+++ b/packages/SystemUI/res/drawable/notification_menu_button_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
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="#202124"/>
+ <corners android:radius="@dimen/notification_corner_radius"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/unpin_icon.xml b/packages/SystemUI/res/drawable/unpin_icon.xml
index 4e2e15893884..979e8d440b78 100644
--- a/packages/SystemUI/res/drawable/unpin_icon.xml
+++ b/packages/SystemUI/res/drawable/unpin_icon.xml
@@ -1,10 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
- android:viewportWidth="960"
+ android:tint="?attr/colorControlNormal"
android:viewportHeight="960"
- android:tint="?attr/colorControlNormal">
- <path
- android:fillColor="@android:color/white"
- android:pathData="M680,120L680,200L640,200L640,527L560,447L560,200L400,200L400,287L313,200L280,167L280,167L280,120L680,120ZM480,920L440,880L440,640L240,640L240,560L320,480L320,434L56,168L112,112L848,848L790,904L526,640L520,640L520,880L480,920ZM354,560L446,560L402,516L400,514L354,560ZM480,367L480,367L480,367L480,367ZM402,516L402,516L402,516L402,516Z"/>
-</vector>
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M680,120L680,200L640,200L640,527L313,200L280,167L280,167L280,120L680,120ZM480,920L440,880L440,640L240,640L240,560L320,480L320,434L56,168L112,112L848,848L790,904L526,640L520,640L520,880L480,920Z" />
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/battery_percentage_view.xml b/packages/SystemUI/res/layout/battery_percentage_view.xml
index 8b5aeaac424a..8aa6930a09f9 100644
--- a/packages/SystemUI/res/layout/battery_percentage_view.xml
+++ b/packages/SystemUI/res/layout/battery_percentage_view.xml
@@ -22,7 +22,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
- android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+ android:textAppearance="@style/TextAppearance.StatusBar.Default"
android:textColor="?android:attr/textColorPrimary"
android:gravity="center_vertical|start"
android:paddingStart="@dimen/battery_level_padding_start"
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index d5f9d4cc0954..a81d90bd13fc 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -76,7 +76,7 @@
android:gravity="center_vertical"
android:ellipsize="marquee"
android:textDirection="locale"
- android:textAppearance="@style/TextAppearance.StatusBar.Carrier"
+ android:textAppearance="@style/TextAppearance.StatusBar.Default"
android:textColor="?attr/wallpaperTextColorSecondary"
android:singleLine="true"
systemui:showMissingSim="true"
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
index 9560be0d6969..56660139f823 100644
--- a/packages/SystemUI/res/layout/notification_conversation_info.xml
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -391,6 +391,16 @@
android:paddingEnd="4dp"
>
<TextView
+ android:id="@+id/inline_dismiss"
+ android:text="@string/notification_inline_dismiss"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:gravity="center_vertical"
+ android:minWidth="@dimen/notification_importance_toggle_size"
+ android:minHeight="@dimen/notification_importance_toggle_size"
+ style="@style/TextAppearance.NotificationInfo.Button"/>
+ <TextView
android:id="@+id/done"
android:text="@string/inline_ok_button"
android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/promoted_menu_item.xml b/packages/SystemUI/res/layout/promoted_menu_item.xml
new file mode 100644
index 000000000000..ec52189585af
--- /dev/null
+++ b/packages/SystemUI/res/layout/promoted_menu_item.xml
@@ -0,0 +1,54 @@
+<?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.
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent">
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_height="match_parent"
+ android:layout_width="@dimen/notification_menu_item_width"
+ android:background="@drawable/notification_menu_button_background"
+ android:backgroundTint="@androidprv:color/materialColorPrimaryContainer"
+ android:padding="@dimen/notification_menu_button_padding">
+ <ImageView
+ android:id="@+id/promoted_menuitem_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:tint="@androidprv:color/materialColorPrimary"
+ android:src="@drawable/unpin_icon" />
+ <TextView
+ android:id="@+id/promoted_menuitem_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/notification_inline_disable_promotion_button"
+ style="@style/TextAppearance.NotificationMenuButtonText"/>
+ <androidx.constraintlayout.helper.widget.Flow
+ android:id="@+id/flow3"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:flow_verticalStyle="packed"
+ app:flow_horizontalAlign="center"
+ app:flow_verticalAlign="center"
+ app:constraint_referenced_ids="promoted_menuitem_icon,promoted_menuitem_text"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"/>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/promoted_permission_guts.xml b/packages/SystemUI/res/layout/promoted_permission_guts.xml
index 50e5ae3c05ed..e962d3ad8679 100644
--- a/packages/SystemUI/res/layout/promoted_permission_guts.xml
+++ b/packages/SystemUI/res/layout/promoted_permission_guts.xml
@@ -54,7 +54,7 @@
android:textColor="@androidprv:color/materialColorOnSurface"
android:minWidth="@dimen/min_clickable_item_size"
android:minHeight="@dimen/min_clickable_item_size"
- style="@style/TextAppearance.NotificationInfo.Button" />
+ style="@style/TextAppearance.NotificationMenuButtonText" />
<TextView
android:id="@+id/undo"
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index e4da4729ad0d..359a69ca1f94 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -96,7 +96,7 @@
android:layout_width="wrap_content"
android:layout_height="@dimen/status_bar_system_icons_height"
android:layout_gravity="center_vertical"
- android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+ android:textAppearance="@style/TextAppearance.StatusBar.Default.Clock"
android:singleLine="true"
android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
diff --git a/packages/SystemUI/res/layout/system_icons.xml b/packages/SystemUI/res/layout/system_icons.xml
index bb99d581c0b0..f3f4e880c121 100644
--- a/packages/SystemUI/res/layout/system_icons.xml
+++ b/packages/SystemUI/res/layout/system_icons.xml
@@ -45,6 +45,6 @@
android:clipChildren="false"
android:paddingEnd="@dimen/status_bar_battery_end_padding"
android:visibility="gone"
- systemui:textAppearance="@style/TextAppearance.StatusBar.Clock" />
+ systemui:textAppearance="@style/TextAppearance.StatusBar.Default" />
</LinearLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 4dfb8cdf7920..ca984881713b 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -300,6 +300,9 @@
<!-- Side padding on the side of notifications -->
<dimen name="notification_side_paddings">16dp</dimen>
+ <!-- Width of inline notification menu item buttons -->
+ <dimen name="notification_menu_item_width">112dp</dimen>
+
<!-- Starting translateY offset of the HUN appear and disappear animations. Indicates
the amount by the view is positioned above the screen before the animation starts. -->
<dimen name="heads_up_appear_y_above_screen">32dp</dimen>
@@ -370,10 +373,12 @@
<dimen name="min_notification_layout_height">48dp</dimen>
<!-- Size of the space to place a notification menu item -->
- <dimen name="notification_menu_icon_size">64dp</dimen>
+ <dimen name="notification_menu_icon_size">120dp</dimen>
<!-- The space around a notification menu item -->
<dimen name="notification_menu_icon_padding">20dp</dimen>
+ <!-- The space around a notification menu button -->
+ <dimen name="notification_menu_button_padding">8dp</dimen>
<!-- scroll view the size of 3 channel rows -->
<dimen name="notification_blocker_channel_list_height">192dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 681bd53f1a40..2d40c32e29e9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2124,6 +2124,9 @@
<!-- [CHAR LIMIT=30] Text shown in button used to prevent app from showing Live Updates, for this notification and all future ones -->
<string name="notification_inline_disable_promotion">Don\'t show as pinned</string>
+ <!-- [CHAR LIMIT=30] Text shown in button used to prevent app from showing Live Updates, for this notification and all future ones -->
+ <string name="notification_inline_disable_promotion_button">Block Live Updates from this app</string>
+
<!-- Text accompanying the "Show live updates" switch explaining the purpose of the setting -->
<string name="live_notifications_title">Showing Live Updates</string>
@@ -4210,9 +4213,9 @@
</string>
- <!-- Content of the Reset Tiles dialog in QS Edit mode. [CHAR LIMIT=NONE] -->
+ <!-- Content of interstitial shown after user revokes app permission to post Live Updates. [CHAR LIMIT=NONE] -->
<string name="demote_explain_text">
- <xliff:g id="application" example= "Superfast Food Delivery">%1$s</xliff:g> will no longer show Live Updates here. You can change this any time in Settings.
+ <xliff:g id="application" example= "Superfast Food Delivery">%1$s</xliff:g> will no longer show Live Updates. You can change this any time in Settings.
</string>
<!-- Template that joins disabled message with the label for the voice over. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index a479f1841ca4..0e1f99f28850 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -17,18 +17,15 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <style name="TextAppearance.StatusBar.Clock" parent="@*android:style/TextAppearance.StatusBar.Icon">
+ <style name="TextAppearance.StatusBar.Default" parent="@*android:style/TextAppearance.StatusBar.Icon">
<item name="android:textSize">@dimen/status_bar_clock_size</item>
<item name="android:fontFamily" android:featureFlag="!com.android.systemui.status_bar_font_updates">@*android:string/config_headlineFontFamilyMedium</item>
<item name="android:fontFamily" android:featureFlag="com.android.systemui.status_bar_font_updates">"variable-label-large-emphasized"</item>
<item name="android:textColor">@color/status_bar_clock_color</item>
- <item name="android:fontFeatureSettings">tnum</item>
</style>
- <style name="TextAppearance.StatusBar.Carrier" parent="@*android:style/TextAppearance.StatusBar.Icon">
- <item name="android:textSize">@dimen/status_bar_clock_size</item>
- <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
- <item name="android:textColor">@color/status_bar_clock_color</item>
+ <style name="TextAppearance.StatusBar.Default.Clock">
+ <item name="android:fontFeatureSettings">tnum</item>
</style>
<style name="TextAppearance.StatusBar.UserChip" parent="@*android:style/TextAppearance.StatusBar.Icon">
@@ -818,6 +815,14 @@
<item name="android:minWidth">0dp</item>
</style>
+ <style name="TextAppearance.NotificationMenuButtonText">
+ <item name="android:textSize">@dimen/notification_importance_header_text</item>
+ <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
+ <item name="android:textColor">@androidprv:color/materialColorOnSurface</item>
+ <item name="android:gravity">center</item>
+ </style>
+
+
<style name="TextAppearance.HeadsUpStatusBarText"
parent="@*android:style/TextAppearance.DeviceDefault.Notification.Info">
</style>
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index f2f177356fab..63189083e22e 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -72,6 +72,7 @@ import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChang
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ZenModeController
import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
+import com.android.systemui.util.annotations.DeprecatedSysuiVisibleForTesting
import com.android.systemui.util.concurrency.DelayableExecutor
import java.util.Locale
import java.util.TimeZone
@@ -392,8 +393,9 @@ constructor(
}
}
- @VisibleForTesting
- internal fun listenForDnd(scope: CoroutineScope): Job {
+ @DeprecatedSysuiVisibleForTesting
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ fun listenForDnd(scope: CoroutineScope): Job {
ModesUi.unsafeAssertInNewMode()
return scope.launch {
zenModeInteractor.dndMode.collect {
@@ -592,8 +594,9 @@ constructor(
dozeAmount.value = doze
}
- @VisibleForTesting
- internal fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
+ @DeprecatedSysuiVisibleForTesting
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ fun listenForDozeAmountTransition(scope: CoroutineScope): Job {
return scope.launch {
merge(
keyguardTransitionInteractor.transition(Edge.create(AOD, LOCKSCREEN)).map {
@@ -609,8 +612,9 @@ constructor(
/**
* When keyguard is displayed again after being gone, the clock must be reset to full dozing.
*/
- @VisibleForTesting
- internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
+ @DeprecatedSysuiVisibleForTesting
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
return scope.launch {
keyguardTransitionInteractor
.transition(Edge.create(to = AOD))
@@ -620,8 +624,9 @@ constructor(
}
}
- @VisibleForTesting
- internal fun listenForAnyStateToLockscreenTransition(scope: CoroutineScope): Job {
+ @DeprecatedSysuiVisibleForTesting
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ fun listenForAnyStateToLockscreenTransition(scope: CoroutineScope): Job {
return scope.launch {
keyguardTransitionInteractor
.transition(Edge.create(to = LOCKSCREEN))
@@ -635,8 +640,9 @@ constructor(
* When keyguard is displayed due to pulsing notifications when AOD is off, we should make sure
* clock is in dozing state instead of LS state
*/
- @VisibleForTesting
- internal fun listenForAnyStateToDozingTransition(scope: CoroutineScope): Job {
+ @DeprecatedSysuiVisibleForTesting
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ fun listenForAnyStateToDozingTransition(scope: CoroutineScope): Job {
return scope.launch {
keyguardTransitionInteractor
.transition(Edge.create(to = DOZING))
diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
index a107322423bb..c5cd39ccbc9f 100644
--- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
@@ -28,6 +28,7 @@ import android.os.ParcelFileDescriptor
import android.os.UserHandle
import android.util.Log
import com.android.app.tracing.traceSection
+import com.android.systemui.Flags
import com.android.systemui.backup.BackupHelper.Companion.ACTION_RESTORE_FINISHED
import com.android.systemui.communal.data.backup.CommunalBackupHelper
import com.android.systemui.communal.data.backup.CommunalBackupUtils
@@ -118,7 +119,9 @@ open class BackupHelper : BackupAgentHelper() {
}
private fun communalEnabled(): Boolean {
- return resources.getBoolean(R.bool.config_communalServiceEnabled)
+ return resources.getBoolean(R.bool.config_communalServiceEnabled) ||
+ (Flags.glanceableHubV2() &&
+ resources.getBoolean(com.android.internal.R.bool.config_glanceableHubEnabled))
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt
index 54f3d7963e61..a871fb6b3d82 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractor.kt
@@ -16,10 +16,12 @@
package com.android.systemui.deviceentry.domain.interactor
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
@@ -31,13 +33,19 @@ class AuthRippleInteractor
constructor(
deviceEntrySourceInteractor: DeviceEntrySourceInteractor,
deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
+ keyguardInteractor: KeyguardInteractor,
) {
+ private val successfulEntryFromDeviceEntryIcon: Flow<Unit> =
+ deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon
+ .map { keyguardInteractor.isKeyguardDismissible.value }
+ .filter { it } // only emit events if the keyguard is dismissible
+ // map to Unit
+ .map {}
+
private val showUnlockRippleFromDeviceEntryIcon: Flow<BiometricUnlockSource> =
deviceEntryUdfpsInteractor.isUdfpsSupported.flatMapLatest { isUdfpsSupported ->
if (isUdfpsSupported) {
- deviceEntrySourceInteractor.deviceEntryFromDeviceEntryIcon.map {
- BiometricUnlockSource.FINGERPRINT_SENSOR
- }
+ successfulEntryFromDeviceEntryIcon.map { BiometricUnlockSource.FINGERPRINT_SENSOR }
} else {
emptyFlow()
}
@@ -46,8 +54,5 @@ constructor(
private val showUnlockRippleFromBiometricUnlock: Flow<BiometricUnlockSource> =
deviceEntrySourceInteractor.deviceEntryFromBiometricSource
val showUnlockRipple: Flow<BiometricUnlockSource> =
- merge(
- showUnlockRippleFromDeviceEntryIcon,
- showUnlockRippleFromBiometricUnlock,
- )
+ merge(showUnlockRippleFromDeviceEntryIcon, showUnlockRippleFromBiometricUnlock)
}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
index 09936839c590..452cc435a36d 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
@@ -16,12 +16,14 @@
package com.android.systemui.deviceentry.domain.interactor
import com.android.keyguard.logging.BiometricUnlockLogger
+import com.android.systemui.Flags
import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
import com.android.systemui.biometrics.shared.model.FingerprintSensorType
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.power.shared.model.WakeSleepReason
import com.android.systemui.util.kotlin.FlowDumperImpl
@@ -54,6 +56,7 @@ constructor(
keyEventInteractor: KeyEventInteractor,
private val logger: BiometricUnlockLogger,
powerInteractor: PowerInteractor,
+ keyguardInteractor: KeyguardInteractor,
private val systemClock: SystemClock,
dumpManager: DumpManager,
) : FlowDumperImpl(dumpManager) {
@@ -80,12 +83,7 @@ constructor(
emit(recentPowerButtonPressThresholdMs * -1L - 1L)
}
- /**
- * Indicates when success haptics should play when the device is entered. This always occurs on
- * successful fingerprint authentications. It also occurs on successful face authentication but
- * only if the lockscreen is bypassed.
- */
- val playSuccessHapticOnDeviceEntry: Flow<Unit> =
+ private val playSuccessHapticOnDeviceEntryFromBiometricSource: Flow<Unit> =
deviceEntrySourceInteractor.deviceEntryFromBiometricSource
.sample(
combine(
@@ -108,7 +106,31 @@ constructor(
}
// map to Unit
.map {}
- .dumpWhileCollecting("playSuccessHaptic")
+
+ private val playSuccessHapticOnDeviceEntryFromDeviceEntryIcon: Flow<Unit> =
+ deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon
+ .map { keyguardInteractor.isKeyguardDismissible.value }
+ .filter { it } // only play if the keyguard is dismissible
+ // map to Unit
+ .map {}
+
+ /**
+ * Indicates when success haptics should play when the device is entered. When entering via a
+ * biometric sources, this always occurs on successful fingerprint authentications. It also
+ * occurs on successful face authentication but only if the lockscreen is bypassed.
+ */
+ val playSuccessHapticOnDeviceEntry: Flow<Unit> =
+ if (Flags.msdlFeedback()) {
+ merge(
+ playSuccessHapticOnDeviceEntryFromBiometricSource,
+ playSuccessHapticOnDeviceEntryFromDeviceEntryIcon,
+ )
+ .dumpWhileCollecting("playSuccessHaptic")
+ } else {
+ playSuccessHapticOnDeviceEntryFromBiometricSource.dumpWhileCollecting(
+ "playSuccessHaptic"
+ )
+ }
private val playErrorHapticForBiometricFailure: Flow<Unit> =
merge(
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt
index 5de2ed231275..19a15fc55976 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntrySourceInteractor.kt
@@ -254,15 +254,12 @@ constructor(
}
.dumpWhileCollecting("deviceEntryFromBiometricSource")
- private val attemptEnterDeviceFromDeviceEntryIcon: MutableSharedFlow<Unit> = MutableSharedFlow()
- val deviceEntryFromDeviceEntryIcon: Flow<Unit> =
- attemptEnterDeviceFromDeviceEntryIcon
- .sample(keyguardInteractor.isKeyguardDismissible)
- .filter { it } // only send events if the keyguard is dismissible
- .map {} // map to Unit
+ private val _attemptEnterDeviceFromDeviceEntryIcon: MutableSharedFlow<Unit> =
+ MutableSharedFlow()
+ val attemptEnterDeviceFromDeviceEntryIcon = _attemptEnterDeviceFromDeviceEntryIcon
suspend fun attemptEnterDeviceFromDeviceEntryIcon() {
- attemptEnterDeviceFromDeviceEntryIcon.emit(Unit)
+ _attemptEnterDeviceFromDeviceEntryIcon.emit(Unit)
}
private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockMode {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
index 1ea47ec670af..0dd7821b4929 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt
@@ -29,6 +29,7 @@ import androidx.core.view.isInvisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.Flags
import com.android.systemui.common.ui.view.TouchHandlingView
import com.android.systemui.keyguard.ui.view.DeviceEntryIconView
import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel
@@ -39,6 +40,8 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.util.kotlin.DisposableHandles
+import com.google.android.msdl.data.model.MSDLToken
+import com.google.android.msdl.domain.MSDLPlayer
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DisposableHandle
@@ -64,6 +67,7 @@ object DeviceEntryIconViewBinder {
bgViewModel: DeviceEntryBackgroundViewModel,
falsingManager: FalsingManager,
vibratorHelper: VibratorHelper,
+ msdlPlayer: MSDLPlayer,
overrideColor: Color? = null,
): DisposableHandle {
val disposables = DisposableHandles()
@@ -88,7 +92,9 @@ object DeviceEntryIconViewBinder {
)
return
}
- vibratorHelper.performHapticFeedback(view, HapticFeedbackConstants.CONFIRM)
+ if (!Flags.msdlFeedback()) {
+ vibratorHelper.performHapticFeedback(view, HapticFeedbackConstants.CONFIRM)
+ }
applicationScope.launch {
view.clearFocus()
view.clearAccessibilityFocus()
@@ -165,10 +171,23 @@ object DeviceEntryIconViewBinder {
view.accessibilityHintType = hint
if (hint != DeviceEntryIconView.AccessibilityHintType.NONE) {
view.setOnClickListener {
- vibratorHelper.performHapticFeedback(
- view,
- HapticFeedbackConstants.CONFIRM,
- )
+ if (Flags.msdlFeedback()) {
+ val token =
+ if (
+ hint ==
+ DeviceEntryIconView.AccessibilityHintType.ENTER
+ ) {
+ MSDLToken.UNLOCK
+ } else {
+ MSDLToken.LONG_PRESS
+ }
+ msdlPlayer.playToken(token)
+ } else {
+ vibratorHelper.performHapticFeedback(
+ view,
+ HapticFeedbackConstants.CONFIRM,
+ )
+ }
applicationScope.launch {
view.clearFocus()
view.clearAccessibilityFocus()
@@ -180,6 +199,16 @@ object DeviceEntryIconViewBinder {
}
}
}
+
+ if (Flags.msdlFeedback()) {
+ launch("$TAG#viewModel.isPrimaryBouncerShowing") {
+ viewModel.deviceDidNotEnterFromDeviceEntryIcon.collect {
+ // If we did not enter from the icon, we did not play device entry
+ // haptics. Therefore, we play the token for long-press instead.
+ msdlPlayer.playToken(MSDLToken.LONG_PRESS)
+ }
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
index 220846d08de7..50ef21b3f14d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -26,6 +26,7 @@ import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launchTraced as launch
+import com.android.systemui.Flags
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.common.ui.binder.TextViewBinder
@@ -59,7 +60,9 @@ object KeyguardSettingsViewBinder {
viewModel.isVisible.distinctUntilChanged().collect { isVisible ->
view.animateVisibility(visible = isVisible)
if (isVisible) {
- vibratorHelper.vibrate(KeyguardBottomAreaVibrations.Activated)
+ if (!Flags.msdlFeedback()) {
+ vibratorHelper.vibrate(KeyguardBottomAreaVibrations.Activated)
+ }
val textView = view.requireViewById(R.id.text) as TextView
view.setOnTouchListener(
KeyguardSettingsButtonOnTouchListener(viewModel = viewModel)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index 9c8f04b419fb..754b3d72c13d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -47,6 +47,7 @@ import com.android.systemui.res.R
import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.shade.ShadeDisplayAware
import com.android.systemui.statusbar.VibratorHelper
+import com.google.android.msdl.domain.MSDLPlayer
import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
@@ -69,6 +70,7 @@ constructor(
private val deviceEntryBackgroundViewModel: Lazy<DeviceEntryBackgroundViewModel>,
private val falsingManager: Lazy<FalsingManager>,
private val vibratorHelper: Lazy<VibratorHelper>,
+ private val msdlPlayer: Lazy<MSDLPlayer>,
@LongPressTouchLog private val logBuffer: LogBuffer,
@KeyguardBlueprintLog blueprintLogBuffer: LogBuffer,
) : KeyguardSection() {
@@ -101,6 +103,7 @@ constructor(
deviceEntryBackgroundViewModel.get(),
falsingManager.get(),
vibratorHelper.get(),
+ msdlPlayer.get(),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
index 9b4bd67f227e..729edcf7e5ba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt
@@ -44,6 +44,7 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filterNot
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
@@ -253,6 +254,13 @@ constructor(
val isLongPressEnabled: Flow<Boolean> = isInteractive
+ val deviceDidNotEnterFromDeviceEntryIcon =
+ deviceEntrySourceInteractor.attemptEnterDeviceFromDeviceEntryIcon
+ .map { keyguardInteractor.isKeyguardDismissible.value }
+ .filterNot { it } // only emit events if the keyguard is not dismissible
+ // map to Unit
+ .map {}
+
suspend fun onUserInteraction() {
if (SceneContainerFlag.isEnabled) {
deviceEntryInteractor.attemptDeviceEntry()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
index 5b65531cdd55..f81745704d2b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/SeekBarObserver.kt
@@ -157,6 +157,7 @@ open class SeekBarObserver(private val holder: MediaViewHolder) :
return DateUtils.formatElapsedTime(milliseconds / DateUtils.SECOND_IN_MILLIS)
}
+ @UiThread
fun updateContentDescription(
elapsedTimeDescription: CharSequence,
durationDescription: CharSequence,
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index f69985ee5364..9cf7356a0ab2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -399,7 +399,9 @@ public class MediaControlPanel {
}
private void setSeekbarContentDescription(CharSequence elapsedTime, CharSequence duration) {
- mSeekBarObserver.updateContentDescription(elapsedTime, duration);
+ mMainExecutor.execute(() -> {
+ mSeekBarObserver.updateContentDescription(elapsedTime, duration);
+ });
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index e87d5de56177..8c683e8f9749 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -236,10 +236,12 @@ constructor(
durationDescription: CharSequence,
) {
if (!SceneContainerFlag.isEnabled) return
- seekBarObserver.updateContentDescription(
- elapsedTimeDescription,
- durationDescription,
- )
+ mainExecutor.execute {
+ seekBarObserver.updateContentDescription(
+ elapsedTimeDescription,
+ durationDescription,
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index efed260b4c99..3f1401cd08b9 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -239,6 +239,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
@Override
public void onDisplayAddSystemDecorations(int displayId) {
CommandQueue.Callbacks.super.onDisplayAddSystemDecorations(displayId);
+ mEdgeBackGestureHandler.onDisplayAddSystemDecorations(displayId);
if (mLauncherProxyService.getProxy() == null) {
return;
}
@@ -253,6 +254,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
@Override
public void onDisplayRemoved(int displayId) {
CommandQueue.Callbacks.super.onDisplayRemoved(displayId);
+ mEdgeBackGestureHandler.onDisplayRemoveSystemDecorations(displayId);
if (mLauncherProxyService.getProxy() == null) {
return;
}
@@ -267,6 +269,7 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
@Override
public void onDisplayRemoveSystemDecorations(int displayId) {
CommandQueue.Callbacks.super.onDisplayRemoveSystemDecorations(displayId);
+ mEdgeBackGestureHandler.onDisplayRemoveSystemDecorations(displayId);
if (mLauncherProxyService.getProxy() == null) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 6cda192c4198..b74135a39ee6 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -22,6 +22,7 @@ import static android.view.MotionEvent.TOOL_TYPE_FINGER;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
import static com.android.systemui.Flags.edgebackGestureHandlerGetRunningTasksBackground;
+import static com.android.window.flags.Flags.enableMultidisplayTrackpadBackGesture;
import static com.android.systemui.Flags.predictiveBackDelayWmTransition;
import static com.android.systemui.classifier.Classifier.BACK_GESTURE;
import static com.android.systemui.navigationbar.gestural.Utilities.isTrackpadThreeFingerSwipe;
@@ -115,6 +116,8 @@ import kotlinx.coroutines.Job;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
@@ -279,8 +282,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
private boolean mIsTrackpadThreeFingerSwipe;
private boolean mIsButtonForcedVisible;
- private InputMonitorCompat mInputMonitor;
- private InputChannelCompat.InputEventReceiver mInputEventReceiver;
+ private final Map<Integer, InputMonitorResource> mInputMonitorResources = new HashMap<>();
private NavigationEdgeBackPlugin mEdgeBackPlugin;
private BackAnimation mBackAnimation;
@@ -665,14 +667,44 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
mIsNavBarShownTransiently = isTransient;
}
- private void disposeInputChannel() {
- if (mInputEventReceiver != null) {
- mInputEventReceiver.dispose();
- mInputEventReceiver = null;
+ /**
+ * Called when a new display gets connected
+ *
+ * @param displayId The id associated with the connected display.
+ */
+ public void onDisplayAddSystemDecorations(int displayId) {
+ if (enableMultidisplayTrackpadBackGesture() && mIsEnabled) {
+ mUiThreadContext.runWithScissors(() -> {
+ removeAndDisposeInputMonitorResource(displayId);
+ mInputMonitorResources.put(displayId, new InputMonitorResource(displayId));
+ });
}
- if (mInputMonitor != null) {
- mInputMonitor.dispose();
- mInputMonitor = null;
+ }
+
+ /**
+ * Called when a display gets disconnected
+ *
+ * @param displayId The id associated with the disconnected display.
+ */
+ public void onDisplayRemoveSystemDecorations(int displayId) {
+ if (enableMultidisplayTrackpadBackGesture()) {
+ mUiThreadContext.runWithScissors(() -> removeAndDisposeInputMonitorResource(displayId));
+ }
+ }
+
+ private void removeAndDisposeInputMonitorResource(int displayId) {
+ InputMonitorResource inputMonitor = mInputMonitorResources.remove(displayId);
+ if (inputMonitor != null) {
+ inputMonitor.dispose();
+ }
+ }
+
+ private void disposeInputChannels() {
+ Iterator<Map.Entry<Integer, InputMonitorResource>> iterator =
+ mInputMonitorResources.entrySet().iterator();
+ while (iterator.hasNext()) {
+ iterator.next().getValue().dispose();
+ iterator.remove();
}
}
@@ -691,7 +723,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
return;
}
mIsEnabled = isEnabled;
- disposeInputChannel();
+ disposeInputChannels();
if (mEdgeBackPlugin != null) {
mEdgeBackPlugin.onDestroy();
@@ -746,9 +778,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
// Register input event receiver
- mInputMonitor = new InputMonitorCompat("edge-swipe", mDisplayId);
- mInputEventReceiver = mInputMonitor.getInputReceiver(mUiThreadContext.getLooper(),
- mUiThreadContext.getChoreographer(), this::onInputEvent);
+ mInputMonitorResources.put(mDisplayId, new InputMonitorResource(mDisplayId));
+ //TODO(b/382774299): Register input monitor on connected displays (if any)
// Add a nav bar panel window
resetEdgeBackPlugin();
@@ -950,9 +981,10 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
return true;
}
- private boolean isValidTrackpadBackGesture(boolean isTrackpadEvent) {
- if (!isTrackpadEvent) {
- return false;
+ private boolean isValidTrackpadBackGesture(int displayId) {
+ if (enableMultidisplayTrackpadBackGesture() && displayId != mDisplayId) {
+ //TODO(b/382774299): Handle exclude regions on connected displays
+ return true;
}
// for trackpad gestures, unless the whole screen is excluded region, 3-finger swipe
// gestures are allowed even if the cursor is in the excluded region.
@@ -969,14 +1001,15 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
private boolean isWithinTouchRegion(MotionEvent ev) {
// If the point is inside the PiP or Nav bar overlay excluded bounds, then ignore the back
- // gesture
+ // gesture. Also ignore (for now) if it's not on the main display.
+ // TODO(b/382130680): Implement back gesture handling on connected displays
int x = (int) ev.getX();
int y = (int) ev.getY();
final boolean isInsidePip = mIsInPip && mPipExcludedBounds.contains(x, y);
final boolean isInDesktopExcludeRegion = desktopExcludeRegionContains(x, y)
&& isEdgeResizePermitted(ev);
if (isInsidePip || isInDesktopExcludeRegion
- || mNavBarOverlayExcludedBounds.contains(x, y)) {
+ || mNavBarOverlayExcludedBounds.contains(x, y) || ev.getDisplayId() != mDisplayId) {
return false;
}
@@ -1076,7 +1109,11 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
// Verify if this is in within the touch region and we aren't in immersive mode, and
// either the bouncer is showing or the notification panel is hidden
- mInputEventReceiver.setBatchingEnabled(false);
+ InputMonitorResource inputMonitorResource =
+ mInputMonitorResources.get(ev.getDisplayId());
+ if (inputMonitorResource != null) {
+ inputMonitorResource.mInputEventReceiver.setBatchingEnabled(false);
+ }
if (mIsTrackpadThreeFingerSwipe) {
// Since trackpad gestures don't have zones, this will be determined later by the
// direction of the gesture. {@code mIsOnLeftEdge} is set to false to begin with.
@@ -1099,7 +1136,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
boolean trackpadGesturesEnabled =
(mSysUiFlags & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) == 0;
mAllowGesture = isBackAllowedCommon && trackpadGesturesEnabled
- && isValidTrackpadBackGesture(true /* isTrackpadEvent */);
+ && isValidTrackpadBackGesture(ev.getDisplayId());
} else {
mAllowGesture = isBackAllowedCommon && !mUsingThreeButtonNav && isWithinInsets
&& isWithinTouchRegion(ev) && !isButtonPressFromTrackpad(ev);
@@ -1210,12 +1247,14 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
private void pilferPointers() {
- if (mInputMonitor != null) {
+ //TODO(b/382774299): Pilfer pointers on the correct display
+ InputMonitorResource inputMonitorResource = mInputMonitorResources.get(mDisplayId);
+ if (inputMonitorResource != null) {
// Capture inputs
- mInputMonitor.pilferPointers();
+ inputMonitorResource.mInputMonitorCompat.pilferPointers();
// Notify FalsingManager that an intentional gesture has occurred.
mFalsingManager.isFalseTouch(BACK_GESTURE);
- mInputEventReceiver.setBatchingEnabled(true);
+ inputMonitorResource.mInputEventReceiver.setBatchingEnabled(true);
}
}
@@ -1344,6 +1383,11 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
if (mEdgeBackPlugin != null) {
mEdgeBackPlugin.dump(pw);
}
+ pw.println(" mInputMonitorResources=" + mInputMonitorResources);
+ for (Map.Entry<Integer, InputMonitorResource> inputMonitorResource :
+ mInputMonitorResources.entrySet()) {
+ inputMonitorResource.getValue().dump("\t", pw);
+ }
}
private void updateTopActivityPackageName() {
@@ -1376,6 +1420,33 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack
}
}
+ private class InputMonitorResource {
+ private final int mDisplayId;
+ private final InputMonitorCompat mInputMonitorCompat;
+ private final InputChannelCompat.InputEventReceiver mInputEventReceiver;
+
+ private InputMonitorResource(int displayId) {
+ this.mDisplayId = displayId;
+ mInputMonitorCompat = new InputMonitorCompat("edge-swipe", displayId);
+ mInputEventReceiver = mInputMonitorCompat.getInputReceiver(mUiThreadContext.getLooper(),
+ mUiThreadContext.getChoreographer(), EdgeBackGestureHandler.this::onInputEvent);
+ }
+
+ public void dispose() {
+ mInputEventReceiver.dispose();
+ mInputMonitorCompat.dispose();
+ }
+
+ public void dump(String prefix, PrintWriter writer) {
+ writer.println(prefix + this);
+ }
+
+ @Override
+ public String toString() {
+ return "InputMonitorResource (displayId=" + mDisplayId + ")";
+ }
+ }
+
private static class LogArray extends ArrayDeque<String> {
private final int mLength;
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 699778f3b6f9..1a0af514cf87 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
@@ -162,6 +162,7 @@ fun LargeTileContent(
colors = colors,
accessibilityUiState = accessibilityUiState,
isVisible = isVisible,
+ modifier = Modifier.weight(1f),
)
if (sideDrawable != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 06fc8610c97b..daaa2db54775 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -332,6 +332,7 @@ constructor(
/** Switches between scenes based on ever-changing application state. */
private fun automaticallySwitchScenes() {
handleBouncerImeVisibility()
+ handleBouncerHiding()
handleSimUnlock()
handleDeviceUnlockStatus()
handlePowerState()
@@ -352,6 +353,24 @@ constructor(
}
}
+ private fun handleBouncerHiding() {
+ applicationScope.launch {
+ repeatWhen(
+ condition =
+ authenticationInteractor
+ .get()
+ .authenticationMethod
+ .map { !it.isSecure }
+ .distinctUntilChanged()
+ ) {
+ sceneInteractor.hideOverlay(
+ overlay = Overlays.Bouncer,
+ loggingReason = "Authentication method changed to a non-secure one.",
+ )
+ }
+ }
+ }
+
private fun handleSimUnlock() {
applicationScope.launch {
simBouncerInteractor
@@ -434,6 +453,12 @@ constructor(
}
}
+ if (powerInteractor.detailedWakefulness.value.isAsleep()) {
+ // The logic below is for when the device becomes unlocked. That must be a
+ // no-op if the device is not awake.
+ return@mapNotNull null
+ }
+
if (
isOnPrimaryBouncer &&
deviceUnlockStatus.deviceUnlockSource == DeviceUnlockSource.TrustAgent
@@ -833,7 +858,7 @@ constructor(
}
.collect {
val loggingReason = "Falsing detected."
- switchToScene(Scenes.Lockscreen, loggingReason)
+ switchToScene(targetSceneKey = Scenes.Lockscreen, loggingReason = loggingReason)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index b211f0729318..82d361797f96 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -101,6 +101,7 @@ constructor(
shadeInteractor.collapseQuickSettingsShade(
loggingReason = "ShadeControllerSceneImpl.instantCollapseShade",
transitionKey = Instant,
+ bypassNotificationsShade = true,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index f844d1da1a8d..50d634f6ac54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -334,6 +334,14 @@ constructor(
private fun onBlurApplied(appliedBlurRadius: Int, zoomOutFromShadeRadius: Float) {
lastAppliedBlur = appliedBlurRadius
+ onZoomOutChanged(zoomOutFromShadeRadius)
+ listeners.forEach { it.onBlurRadiusChanged(appliedBlurRadius) }
+ notificationShadeWindowController.setBackgroundBlurRadius(appliedBlurRadius)
+ }
+
+ private fun onZoomOutChanged(zoomOutFromShadeRadius: Float) {
+ TrackTracer.instantForGroup("shade", "zoom_out", zoomOutFromShadeRadius)
+ Log.v(TAG, "onZoomOutChanged $zoomOutFromShadeRadius")
wallpaperController.setNotificationShadeZoom(zoomOutFromShadeRadius)
if (spatialModelAppPushback()) {
appZoomOutOptional.ifPresent { appZoomOut ->
@@ -341,12 +349,15 @@ constructor(
}
keyguardInteractor.setZoomOut(zoomOutFromShadeRadius)
}
- listeners.forEach {
- it.onBlurRadiusChanged(appliedBlurRadius)
- }
- notificationShadeWindowController.setBackgroundBlurRadius(appliedBlurRadius)
}
+ private val applyZoomOutForFrame =
+ Choreographer.FrameCallback {
+ updateScheduled = false
+ val (_, zoomOutFromShadeRadius) = computeBlurAndZoomOut()
+ onZoomOutChanged(zoomOutFromShadeRadius)
+ }
+
/** Animate blurs when unlocking. */
private val keyguardStateCallback =
object : KeyguardStateController.Callback {
@@ -627,8 +638,17 @@ constructor(
val (blur, zoomOutFromShadeRadius) = computeBlurAndZoomOut()
zoomOutCalculatedFromShadeRadius = zoomOutFromShadeRadius
if (Flags.bouncerUiRevamp() || Flags.glanceableHubBlurredBackground()) {
- updateScheduled =
- windowRootViewBlurInteractor.requestBlurForShade(blur, shouldBlurBeOpaque)
+ if (windowRootViewBlurInteractor.isBlurCurrentlySupported.value) {
+ updateScheduled =
+ windowRootViewBlurInteractor.requestBlurForShade(blur, shouldBlurBeOpaque)
+ return
+ }
+ // When blur is not supported, zoom out still needs to happen when scheduleUpdate
+ // is invoked and a separate frame callback has to be wired-up to support that.
+ if (!updateScheduled) {
+ updateScheduled = true
+ choreographer.postFrameCallback(applyZoomOutForFrame)
+ }
return
}
if (updateScheduled) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
index 6d3c12d139db..0ebe194018cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
@@ -6,8 +6,8 @@ caitlinshk@google.com
evanlaird@google.com
pixel@google.com
-per-file *Biometrics* = set noparent
-per-file *Biometrics* = file:../keyguard/OWNERS
+per-file *Biometric* = set noparent
+per-file *Biometric* = file:../keyguard/OWNERS
per-file *Doze* = set noparent
per-file *Doze* = file:../keyguard/OWNERS
per-file *Keyboard* = set noparent
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt
index 6431f303089f..5b989d8e1e7c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt
@@ -16,15 +16,18 @@
package com.android.systemui.statusbar.chips.notification.shared
-import com.android.systemui.Flags
+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 status bar promoted notification chips flag state. */
@Suppress("NOTHING_TO_INLINE")
object StatusBarNotifChips {
/** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_STATUS_BAR_NOTIFICATION_CHIPS
+ const val FLAG_NAME = Flags.FLAG_UI_RICH_ONGOING
/** A token used for dependency declaration */
val token: FlagToken
@@ -33,7 +36,7 @@ object StatusBarNotifChips {
/** Is the refactor enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.statusBarNotificationChips()
+ get() = Flags.uiRichOngoing()
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
index 104c2b546200..167035b2d17d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/compose/OngoingActivityChip.kt
@@ -16,8 +16,11 @@
package com.android.systemui.statusbar.chips.ui.compose
+import android.annotation.IdRes
import android.content.res.ColorStateList
+import android.util.Log
import android.view.ViewGroup
+import android.widget.FrameLayout
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
@@ -234,10 +237,35 @@ private fun StatusBarIcon(
AndroidView(
modifier = modifier,
factory = { _ ->
- iconFactory.invoke()?.apply {
- layoutParams = ViewGroup.LayoutParams(iconSizePx, iconSizePx)
- } ?: throw IllegalStateException("Missing StatusBarIconView for $notificationKey")
+ // Use a wrapper frame layout so that we still return a view even if the icon is null
+ val wrapperFrameLayout = FrameLayout(context)
+
+ val icon = iconFactory.invoke()
+ if (icon == null) {
+ Log.e(TAG, "Missing StatusBarIconView for $notificationKey")
+ } else {
+ icon.apply {
+ id = CUSTOM_ICON_VIEW_ID
+ layoutParams = ViewGroup.LayoutParams(iconSizePx, iconSizePx)
+ }
+ // If needed, remove the icon from its old parent (views can only be attached
+ // to 1 parent at a time)
+ (icon.parent as? ViewGroup)?.apply {
+ this.removeView(icon)
+ this.removeTransientView(icon)
+ }
+ wrapperFrameLayout.addView(icon)
+ }
+
+ wrapperFrameLayout
+ },
+ update = { frameLayout ->
+ frameLayout.findViewById<StatusBarIconView>(CUSTOM_ICON_VIEW_ID)?.apply {
+ this.imageTintList = colorTintList
+ }
},
- update = { iconView -> iconView.imageTintList = colorTintList },
)
}
+
+private const val TAG = "OngoingActivityChip"
+@IdRes private val CUSTOM_ICON_VIEW_ID = R.id.ongoing_activity_chip_custom_icon
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
index 8fc6cbe7c9e7..e69de29bb2d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.java
@@ -1,110 +0,0 @@
-/*
- * 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.statusbar.notification.collection;
-
-import static android.app.NotificationChannel.NEWS_ID;
-import static android.app.NotificationChannel.PROMOTIONS_ID;
-import static android.app.NotificationChannel.RECS_ID;
-import static android.app.NotificationChannel.SOCIAL_MEDIA_ID;
-
-import android.app.Notification;
-import android.content.Context;
-import android.os.Build;
-import android.service.notification.StatusBarNotification;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-import com.android.systemui.statusbar.notification.icon.IconPack;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.flow.StateFlow;
-import kotlinx.coroutines.flow.StateFlowKt;
-
-/**
- * Class to represent notifications bundled by classification.
- */
-public class BundleEntry extends PipelineEntry {
-
- // TODO(b/394483200): move NotificationEntry's implementation to PipelineEntry?
- private final MutableStateFlow<Boolean> mSensitive = StateFlowKt.MutableStateFlow(false);
-
- // TODO (b/389839319): implement the row
- private ExpandableNotificationRow mRow;
-
- private final List<ListEntry> mChildren = new ArrayList<>();
-
- private final List<ListEntry> mUnmodifiableChildren = Collections.unmodifiableList(mChildren);
-
- public BundleEntry(String key) {
- super(key);
- }
-
- void addChild(ListEntry child) {
- mChildren.add(child);
- }
-
- @NonNull
- public List<ListEntry> getChildren() {
- return mUnmodifiableChildren;
- }
-
- void clearChildren() {
- mChildren.clear();
- }
-
- /**
- * @return Null because bundles do not have an associated NotificationEntry.
- */
- @Nullable
- @Override
- public NotificationEntry getRepresentativeEntry() {
- return null;
- }
-
- @Nullable
- @Override
- public PipelineEntry getParent() {
- return null;
- }
-
- @Override
- public boolean wasAttachedInPreviousPass() {
- return false;
- }
-
- @Nullable
- public ExpandableNotificationRow getRow() {
- return mRow;
- }
-
- public static final List<BundleEntry> ROOT_BUNDLES = List.of(
- new BundleEntry(PROMOTIONS_ID),
- new BundleEntry(SOCIAL_MEDIA_ID),
- new BundleEntry(NEWS_ID),
- new BundleEntry(RECS_ID));
-
- public MutableStateFlow<Boolean> isSensitive() {
- return mSensitive;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.kt
new file mode 100644
index 000000000000..0da76c333a1f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntry.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.statusbar.notification.collection
+
+import android.app.NotificationChannel
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import java.util.Collections
+import kotlinx.coroutines.flow.MutableStateFlow
+
+/** Class to represent notifications bundled by classification. */
+class BundleEntry(key: String) : PipelineEntry(key) {
+ // TODO(b/394483200): move NotificationEntry's implementation to PipelineEntry?
+ val isSensitive: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ // TODO (b/389839319): implement the row
+ val row: ExpandableNotificationRow? = null
+
+ private val _children: MutableList<ListEntry> = ArrayList()
+ val children: List<ListEntry> = Collections.unmodifiableList(_children)
+
+ fun addChild(child: ListEntry) {
+ _children.add(child)
+ }
+
+ fun clearChildren() {
+ _children.clear()
+ }
+
+ /** @return Null because bundles do not have an associated NotificationEntry. */
+ override fun getRepresentativeEntry(): NotificationEntry? {
+ return null
+ }
+
+ override fun getParent(): PipelineEntry? {
+ return null
+ }
+
+ override fun wasAttachedInPreviousPass(): Boolean {
+ return false
+ }
+
+ companion object {
+ val ROOT_BUNDLES: List<BundleEntry> =
+ listOf(
+ BundleEntry(NotificationChannel.PROMOTIONS_ID),
+ BundleEntry(NotificationChannel.SOCIAL_MEDIA_ID),
+ BundleEntry(NotificationChannel.NEWS_ID),
+ BundleEntry(NotificationChannel.RECS_ID),
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt
index 6a3f8f166c34..98714aeb1248 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/BundleEntryAdapter.kt
@@ -33,6 +33,11 @@ class BundleEntryAdapter(
private val highPriorityProvider: HighPriorityProvider,
val entry: BundleEntry,
) : EntryAdapter {
+
+ override fun getBackingHashCode(): Int {
+ return entry.hashCode()
+ }
+
/** TODO (b/394483200): convert to PipelineEntry.ROOT_ENTRY when pipeline is migrated? */
override fun getParent(): GroupEntry {
return GroupEntry.ROOT_ENTRY
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
index 16d9c787d435..43ae4d9296c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryAdapter.java
@@ -36,6 +36,11 @@ import kotlinx.coroutines.flow.StateFlow;
public interface EntryAdapter {
/**
+ * Returns the hash code of the backing entry
+ */
+ int getBackingHashCode();
+
+ /**
* Gets the parent of this entry, or null if the entry's view is not attached
*/
@Nullable PipelineEntry getParent();
@@ -195,5 +200,6 @@ public interface EntryAdapter {
NotificationEntry.DismissState getDismissState();
void onEntryClicked(ExpandableNotificationRow row);
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryWithDismissStats.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryWithDismissStats.kt
index 48a8c01e7c47..e37a210c1c2a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryWithDismissStats.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/EntryWithDismissStats.kt
@@ -23,7 +23,10 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Di
* A holder class for a [NotificationEntry] and an associated [DismissedByUserStats], used by
* [NotifCollection] for handling dismissal.
*/
-data class EntryWithDismissStats(val entry: NotificationEntry, val stats: DismissedByUserStats) {
+data class EntryWithDismissStats(val entry: NotificationEntry?,
+ val stats: DismissedByUserStats,
+ val key: String,
+ val entryHashCode: Int) {
/**
* Creates deep a copy of this object, but with the entry, key and rank updated to correspond to
* the given entry.
@@ -42,5 +45,7 @@ data class EntryWithDismissStats(val entry: NotificationEntry, val stats: Dismis
/* visible= */ false,
),
),
+ key = newEntry.key,
+ entryHashCode = newEntry.hashCode()
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index b7fe39e9c757..10d7b9cce559 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -98,6 +98,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.Ra
import com.android.systemui.statusbar.notification.collection.notifcollection.RankingUpdatedEvent;
import com.android.systemui.statusbar.notification.collection.notifcollection.UpdateSource;
import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
+import com.android.systemui.statusbar.notification.shared.NotificationBundleUi;
import com.android.systemui.util.Assert;
import com.android.systemui.util.NamedListenerSet;
import com.android.systemui.util.time.SystemClock;
@@ -283,53 +284,55 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
final int entryCount = entriesToDismiss.size();
final List<NotificationEntry> entriesToLocallyDismiss = new ArrayList<>();
for (int i = 0; i < entriesToDismiss.size(); i++) {
- NotificationEntry entry = entriesToDismiss.get(i).getEntry();
+ String key = entriesToDismiss.get(i).getKey();
+ int hashCode = entriesToDismiss.get(i).getEntryHashCode();
DismissedByUserStats stats = entriesToDismiss.get(i).getStats();
requireNonNull(stats);
- NotificationEntry storedEntry = mNotificationSet.get(entry.getKey());
+ NotificationEntry storedEntry = mNotificationSet.get(key);
if (storedEntry == null) {
- mLogger.logDismissNonExistentNotif(entry, i, entryCount);
+ mLogger.logDismissNonExistentNotif(key, i, entryCount);
continue;
}
- if (entry != storedEntry) {
+ if (hashCode != storedEntry.hashCode()) {
throw mEulogizer.record(
new IllegalStateException("Invalid entry: "
- + "different stored and dismissed entries for " + logKey(entry)
+ + "different stored and dismissed entries for " + logKey(key)
+ " (" + i + "/" + entryCount + ")"
- + " dismissed=@" + Integer.toHexString(entry.hashCode())
+ + " dismissed=@" + Integer.toHexString(hashCode)
+ " stored=@" + Integer.toHexString(storedEntry.hashCode())));
}
- if (entry.getDismissState() == DISMISSED) {
- mLogger.logDismissAlreadyDismissedNotif(entry, i, entryCount);
+ if (storedEntry.getDismissState() == DISMISSED) {
+ mLogger.logDismissAlreadyDismissedNotif(storedEntry, i, entryCount);
continue;
- } else if (entry.getDismissState() == PARENT_DISMISSED) {
- mLogger.logDismissAlreadyParentDismissedNotif(entry, i, entryCount);
+ } else if (storedEntry.getDismissState() == PARENT_DISMISSED) {
+ mLogger.logDismissAlreadyParentDismissedNotif(storedEntry, i, entryCount);
}
- updateDismissInterceptors(entry);
- if (isDismissIntercepted(entry)) {
- mLogger.logNotifDismissedIntercepted(entry, i, entryCount);
+ updateDismissInterceptors(storedEntry);
+ if (isDismissIntercepted(storedEntry)) {
+ mLogger.logNotifDismissedIntercepted(storedEntry, i, entryCount);
continue;
}
- entriesToLocallyDismiss.add(entry);
- if (!entry.isCanceled()) {
+ entriesToLocallyDismiss.add(storedEntry);
+ if (!storedEntry.isCanceled()) {
int finalI = i;
// send message to system server if this notification hasn't already been cancelled
mBgExecutor.execute(() -> {
try {
mStatusBarService.onNotificationClear(
- entry.getSbn().getPackageName(),
- entry.getSbn().getUser().getIdentifier(),
- entry.getSbn().getKey(),
+ storedEntry.getSbn().getPackageName(),
+ storedEntry.getSbn().getUser().getIdentifier(),
+ storedEntry.getSbn().getKey(),
stats.dismissalSurface,
stats.dismissalSentiment,
stats.notificationVisibility);
} catch (RemoteException e) {
// system process is dead if we're here.
- mLogger.logRemoteExceptionOnNotificationClear(entry, finalI, entryCount, e);
+ mLogger.logRemoteExceptionOnNotificationClear(
+ storedEntry, finalI, entryCount, e);
}
});
}
@@ -343,28 +346,43 @@ public class NotifCollection implements Dumpable, PipelineDumpable {
List<EntryWithDismissStats> entriesToDismiss) {
final HashSet<NotificationEntry> entriesSet = new HashSet<>(entriesToDismiss.size());
for (EntryWithDismissStats entryToStats : entriesToDismiss) {
- entriesSet.add(entryToStats.getEntry());
+ NotificationEntry entry = getEntryFromDismissalStats(entryToStats);
+ if (entry != null) {
+ entriesSet.add(entry);
+ }
}
final List<EntryWithDismissStats> entriesPlusSummaries =
new ArrayList<>(entriesToDismiss.size() + 1);
for (EntryWithDismissStats entryToStats : entriesToDismiss) {
entriesPlusSummaries.add(entryToStats);
- NotificationEntry summary = fetchSummaryToDismiss(entryToStats.getEntry());
- if (summary != null && !entriesSet.contains(summary)) {
- entriesPlusSummaries.add(entryToStats.copyForEntry(summary));
+ NotificationEntry entry = getEntryFromDismissalStats(entryToStats);
+ if (entry != null) {
+ NotificationEntry summary = fetchSummaryToDismiss(entry);
+ if (summary != null && !entriesSet.contains(summary)) {
+ entriesPlusSummaries.add(entryToStats.copyForEntry(summary));
+ }
}
}
return entriesPlusSummaries;
}
+ private NotificationEntry getEntryFromDismissalStats(EntryWithDismissStats stats) {
+ if (NotificationBundleUi.isEnabled()) {
+ return mNotificationSet.get(stats.getKey());
+ } else {
+ return stats.getEntry();
+ }
+ }
+
/**
* Dismisses a single notification on behalf of the user.
*/
public void dismissNotification(
NotificationEntry entry,
@NonNull DismissedByUserStats stats) {
- dismissNotifications(List.of(new EntryWithDismissStats(entry, stats)));
+ dismissNotifications(List.of(new EntryWithDismissStats(
+ entry, stats, entry.getKey(), entry.hashCode())));
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt
index 339a999e1535..b8b4e9886c66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntryAdapter.kt
@@ -44,6 +44,9 @@ class NotificationEntryAdapter(
private val headsUpManager: HeadsUpManager,
private val entry: NotificationEntry,
) : EntryAdapter {
+ override fun getBackingHashCode(): Int {
+ return entry.hashCode()
+ }
override fun getParent(): PipelineEntry? {
return entry.parent
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index ae2c70a284e9..cfd42d5a5cae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -119,9 +119,9 @@ class NotifCollectionLogger @Inject constructor(
})
}
- fun logDismissNonExistentNotif(entry: NotificationEntry, index: Int, count: Int) {
+ fun logDismissNonExistentNotif(entryKey: String, index: Int, count: Int) {
buffer.log(TAG, INFO, {
- str1 = entry.logKey
+ str1 = logKey(entryKey)
int1 = index
int2 = count
}, {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt
index c6e3da1c5750..69e27dcc2e6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiAod.kt
@@ -16,14 +16,17 @@
package com.android.systemui.statusbar.notification.promoted
-import com.android.systemui.Flags
+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 promoted ongoing notifications AOD flag state. */
object PromotedNotificationUiAod {
/** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_AOD_UI_RICH_ONGOING
+ const val FLAG_NAME = Flags.FLAG_UI_RICH_ONGOING
/** A token used for dependency declaration */
val token: FlagToken
@@ -32,7 +35,7 @@ object PromotedNotificationUiAod {
/** Is the refactor enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.aodUiRichOngoing()
+ get() = Flags.uiRichOngoing()
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
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
index adeddde8ccc3..5c0991059dec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationUiForceExpanded.kt
@@ -16,15 +16,18 @@
package com.android.systemui.statusbar.notification.promoted
-import com.android.systemui.Flags
+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_FORCE_EXPANDED
+ const val FLAG_NAME = Flags.FLAG_UI_RICH_ONGOING
/** A token used for dependency declaration */
val token: FlagToken
@@ -33,7 +36,7 @@ object PromotedNotificationUiForceExpanded {
/** Is the refactor enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.uiRichOngoingForceExpanded()
+ get() = Flags.uiRichOngoing()
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
index d9778bdde0a5..fa9a7b9b524e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractor.kt
@@ -18,10 +18,13 @@ package com.android.systemui.statusbar.notification.promoted.domain.interactor
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.statusbar.notification.promoted.shared.model.PromotedNotificationContentModel
+import com.android.systemui.statusbar.policy.domain.interactor.SensitiveNotificationProtectionInteractor
import com.android.systemui.util.kotlin.FlowDumperImpl
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@@ -30,14 +33,31 @@ class AODPromotedNotificationInteractor
@Inject
constructor(
promotedNotificationsInteractor: PromotedNotificationsInteractor,
+ keyguardInteractor: KeyguardInteractor,
+ sensitiveNotificationProtectionInteractor: SensitiveNotificationProtectionInteractor,
dumpManager: DumpManager,
) : FlowDumperImpl(dumpManager) {
+
+ /**
+ * Whether the system is unlocked and not screensharing such that private notification content
+ * is allowed to show on the aod
+ */
+ private val canShowPrivateNotificationContent: Flow<Boolean> =
+ combine(
+ keyguardInteractor.isKeyguardDismissible,
+ sensitiveNotificationProtectionInteractor.isSensitiveStateActive,
+ ) { isKeyguardDismissible, isSensitive ->
+ isKeyguardDismissible && !isSensitive
+ }
+
/** The content to show as the promoted notification on AOD */
val content: Flow<PromotedNotificationContentModel?> =
- promotedNotificationsInteractor.aodPromotedNotification
- .map {
- // TODO(b/400991304): show the private version when unlocked
- it?.publicVersion
+ combine(
+ promotedNotificationsInteractor.aodPromotedNotification,
+ canShowPrivateNotificationContent,
+ ) { promotedContent, showPrivateContent ->
+ if (showPrivateContent) promotedContent?.privateVersion
+ else promotedContent?.publicVersion
}
.distinctUntilNewInstance()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 2cf3b14bb8c5..0257b4c2397e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -218,7 +218,7 @@ public class NotificationConversationInfo extends LinearLayout implements
@Background Handler bgHandler,
OnConversationSettingsClickListener onConversationSettingsClickListener,
Optional<BubblesManager> bubblesManagerOptional,
- ShadeController shadeController) {
+ ShadeController shadeController, boolean isDismissable, OnClickListener onCloseClick) {
mINotificationManager = iNotificationManager;
mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
mOnUserInteractionCallback = onUserInteractionCallback;
@@ -263,6 +263,11 @@ public class NotificationConversationInfo extends LinearLayout implements
bindHeader();
bindActions();
+ View dismissButton = findViewById(R.id.inline_dismiss);
+ dismissButton.setOnClickListener(onCloseClick);
+ dismissButton.setVisibility(dismissButton.hasOnClickListeners() && isDismissable
+ ? VISIBLE : GONE);
+
View done = findViewById(R.id.done);
done.setOnClickListener(mOnDone);
done.setAccessibilityDelegate(mGutsContainer.getAccessibilityDelegate());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 6c7c7a79348f..d0567f08c2f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -608,7 +608,9 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta
mBgHandler,
onConversationSettingsListener,
mBubblesManagerOptional,
- mShadeController);
+ mShadeController,
+ row.canViewBeDismissed(),
+ row.getCloseButtonOnClickListener(row));
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index f494a4ce40dd..2e3a95e07083 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -39,6 +39,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.FrameLayout.LayoutParams;
+import android.widget.ImageView;
import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
@@ -485,19 +486,23 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
@Override
public void onParentHeightUpdate() {
- if (mParent == null
- || (mLeftMenuItems.isEmpty() && mRightMenuItems.isEmpty())
- || mMenuContainer == null) {
- return;
- }
- int parentHeight = mParent.getActualHeight();
- float translationY;
- if (parentHeight < mVertSpaceForIcons) {
- translationY = (parentHeight / 2) - (mHorizSpaceForIcon / 2);
- } else {
- translationY = (mVertSpaceForIcons - mHorizSpaceForIcon) / 2;
+ // If we are using only icon-based buttons, adjust layout for height changes.
+ // For permission helper full-layout buttons, do not adjust.
+ if (!Flags.permissionHelperInlineUiRichOngoing()) {
+ if (mParent == null
+ || (mLeftMenuItems.isEmpty() && mRightMenuItems.isEmpty())
+ || mMenuContainer == null) {
+ return;
+ }
+ int parentHeight = mParent.getActualHeight();
+ float translationY;
+ if (parentHeight < mVertSpaceForIcons) {
+ translationY = (parentHeight / 2) - (mHorizSpaceForIcon / 2);
+ } else {
+ translationY = (mVertSpaceForIcons - mHorizSpaceForIcon) / 2;
+ }
+ mMenuContainer.setTranslationY(translationY);
}
- mMenuContainer.setTranslationY(translationY);
}
@Override
@@ -697,8 +702,11 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
PromotedPermissionGutsContent demoteContent =
(PromotedPermissionGutsContent) LayoutInflater.from(context).inflate(
R.layout.promoted_permission_guts, null, false);
+ View demoteButton = LayoutInflater.from(context)
+ .inflate(R.layout.promoted_menu_item, null, false);
MenuItem info = new NotificationMenuItem(context, null, demoteContent,
- R.drawable.unpin_icon);
+ demoteButton);
+
return info;
}
@@ -758,10 +766,12 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
menuView.setAlpha(mAlpha);
parent.addView(menuView);
menuView.setOnClickListener(this);
- FrameLayout.LayoutParams lp = (LayoutParams) menuView.getLayoutParams();
- lp.width = mHorizSpaceForIcon;
- lp.height = mHorizSpaceForIcon;
- menuView.setLayoutParams(lp);
+ if (item instanceof ImageView) {
+ FrameLayout.LayoutParams lp = (LayoutParams) menuView.getLayoutParams();
+ lp.width = mHorizSpaceForIcon;
+ lp.height = mHorizSpaceForIcon;
+ menuView.setLayoutParams(lp);
+ }
}
mMenuItemsByView.put(menuView, item);
}
@@ -860,6 +870,17 @@ public class NotificationMenuRow implements NotificationMenuRowPlugin, View.OnCl
mGutsContent = content;
}
+
+ /**
+ * Add a new 'guts' panel with custom view.
+ */
+ public NotificationMenuItem(Context context, String contentDescription, GutsContent content,
+ View itemView) {
+ mMenuView = itemView;
+ mContentDescription = contentDescription;
+ mGutsContent = content;
+ }
+
@Override
@Nullable
public View getMenuView() {
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 19321dcef5c7..d5e2e7eb3a9c 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
@@ -43,7 +43,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ContrastColorUtil;
import com.android.internal.widget.NotificationActionListLayout;
import com.android.systemui.Dependency;
-import com.android.systemui.Flags;
import com.android.systemui.UiOffloadThread;
import com.android.systemui.res.R;
import com.android.systemui.statusbar.CrossFadeHelper;
@@ -51,6 +50,7 @@ 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,7 +196,8 @@ public class NotificationTemplateViewWrapper extends NotificationHeaderViewWrapp
}
private void adjustTitleAndRightIconForPromotedOngoing() {
- if (Flags.uiRichOngoingForceExpanded() && mRow.isPromotedOngoing() && mRightIcon != null) {
+ if (PromotedNotificationUiForceExpanded.isEnabled() &&
+ mRow.isPromotedOngoing() && mRightIcon != null) {
final int horizontalMargin;
if (notificationsRedesignTemplates()) {
horizontalMargin = mView.getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 66c9b17ef235..7ac7905c8a48 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -97,6 +97,7 @@ import com.android.systemui.statusbar.notification.ColorUpdateLogger;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.collection.EntryAdapter;
import com.android.systemui.statusbar.notification.collection.EntryWithDismissStats;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -1654,6 +1655,13 @@ public class NotificationStackScrollLayoutController implements Dumpable {
mVisibilityProvider.obtain(entry, true));
}
+ private DismissedByUserStats getDismissedByUserStats(String entryKey) {
+ return new DismissedByUserStats(
+ DISMISSAL_SHADE,
+ DISMISS_SENTIMENT_NEUTRAL,
+ mVisibilityProvider.obtain(entryKey, true));
+ }
+
private View getGutsView() {
NotificationGuts guts = mNotificationGutsManager.getExposedGuts();
NotificationMenuRowPlugin menuRow = mSwipeHelper.getCurrentMenuRow();
@@ -1705,9 +1713,19 @@ public class NotificationStackScrollLayoutController implements Dumpable {
final List<EntryWithDismissStats>
entriesWithRowsDismissedFromShade = new ArrayList<>();
for (ExpandableNotificationRow row : viewsToRemove) {
- final NotificationEntry entry = row.getEntry();
- entriesWithRowsDismissedFromShade.add(
- new EntryWithDismissStats(entry, getDismissedByUserStats(entry)));
+ if (NotificationBundleUi.isEnabled()) {
+ EntryAdapter entryAdapter = row.getEntryAdapter();
+ entriesWithRowsDismissedFromShade.add(
+ new EntryWithDismissStats(null,
+ getDismissedByUserStats(entryAdapter.getKey()),
+ entryAdapter.getKey(),
+ entryAdapter.getBackingHashCode()));
+ } else {
+ final NotificationEntry entry = row.getEntryLegacy();
+ entriesWithRowsDismissedFromShade.add(
+ new EntryWithDismissStats(entry, getDismissedByUserStats(entry),
+ entry.getKey(), entry.hashCode()));
+ }
}
mNotifCollection.dismissNotifications(entriesWithRowsDismissedFromShade);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 8a5b22183563..8f1d59c62844 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone;
import static android.app.StatusBarManager.SESSION_KEYGUARD;
+import static android.service.dreams.Flags.dreamsV2;
import android.annotation.IntDef;
import android.content.res.Resources;
@@ -662,6 +663,9 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
final boolean deviceDreaming = mUpdateMonitor.isDreaming();
final boolean bypass = mKeyguardBypassController.getBypassEnabled()
|| mAuthController.isUdfpsFingerDown();
+ final boolean isBouncerShowing = mKeyguardViewController.primaryBouncerIsOrWillBeShowing()
+ || mKeyguardTransitionInteractor.getCurrentState()
+ == KeyguardState.ALTERNATE_BOUNCER;
logCalculateModeForPassiveAuth(unlockingAllowed, deviceInteractive, isKeyguardShowing,
deviceDreaming, bypass, isStrongBiometric);
@@ -685,15 +689,14 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
}
}
if (unlockingAllowed && deviceDreaming) {
- return bypass ? MODE_WAKE_AND_UNLOCK_FROM_DREAM : MODE_ONLY_WAKE;
+ final boolean wakeAndUnlock = bypass || (dreamsV2() && isBouncerShowing);
+ return wakeAndUnlock ? MODE_WAKE_AND_UNLOCK_FROM_DREAM : MODE_ONLY_WAKE;
}
if (unlockingAllowed && mKeyguardStateController.isOccluded()) {
return MODE_UNLOCK_COLLAPSING;
}
if (isKeyguardShowing) {
- if ((mKeyguardViewController.primaryBouncerIsOrWillBeShowing()
- || mKeyguardTransitionInteractor.getCurrentState()
- == KeyguardState.ALTERNATE_BOUNCER) && unlockingAllowed) {
+ if (isBouncerShowing && unlockingAllowed) {
return MODE_DISMISS_BOUNCER;
} else if (unlockingAllowed && bypass) {
return MODE_UNLOCK_COLLAPSING;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index fa4fe46e690c..83e5db4db6fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -459,7 +459,7 @@ public class KeyguardStatusBarView extends RelativeLayout {
/** Should only be called from {@link KeyguardStatusBarViewController}. */
void onOverlayChanged() {
- final int carrierTheme = R.style.TextAppearance_StatusBar_Clock;
+ final int carrierTheme = R.style.TextAppearance_StatusBar_Default;
mCarrierLabel.setTextAppearance(carrierTheme);
if (mBatteryView != null) {
mBatteryView.updatePercentView();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/SensitiveNotificationProtectionInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/SensitiveNotificationProtectionInteractor.kt
new file mode 100644
index 000000000000..0a6a4c2e44e7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/domain/interactor/SensitiveNotificationProtectionInteractor.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.statusbar.policy.domain.interactor
+
+import com.android.server.notification.Flags
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+
+/** A interactor which provides the current sensitive notification protections status */
+@SysUISingleton
+class SensitiveNotificationProtectionInteractor
+@Inject
+constructor(private val controller: SensitiveNotificationProtectionController) {
+
+ /** sensitive notification protections status */
+ val isSensitiveStateActive: Flow<Boolean> =
+ if (Flags.screenshareNotificationHiding()) {
+ conflatedCallbackFlow {
+ val listener = Runnable { trySend(controller.isSensitiveStateActive) }
+ controller.registerSensitiveStateListener(listener)
+ trySend(controller.isSensitiveStateActive)
+ awaitClose { controller.unregisterSensitiveStateListener(listener) }
+ }
+ .distinctUntilChanged()
+ } else {
+ flowOf(false)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
index 36d64a9b405e..bc3eb23f5d09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowView.java
@@ -34,6 +34,7 @@ import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.systemui.Flags;
import com.android.systemui.compose.ComposeInitializer;
import com.android.systemui.statusbar.core.StatusBarRootModernization;
import com.android.systemui.statusbar.data.repository.StatusBarConfigurationController;
@@ -117,9 +118,15 @@ public class StatusBarWindowView extends FrameLayout {
* bound of the status bar view, in order for the touch event to be correctly dispatched down,
* we jot down the position Y of the initial touch down event, offset it to 0 in the y-axis,
* and calculate the movement based on first touch down position.
+ *
+ * TODO(b/391894499): Remove this doc once Flags.statusBarWindowNoCustomTouch() is rolled out.
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
+ if (Flags.statusBarWindowNoCustomTouch()) {
+ return super.dispatchTouchEvent(ev);
+ }
+
if (ev.getAction() == ACTION_DOWN && ev.getRawY() > getHeight()) {
mTouchDownY = ev.getRawY();
ev.setLocation(ev.getRawX(), mTopInset);
diff --git a/packages/SystemUI/src/com/android/systemui/util/annotations/DeprecatedSysuiVisibleForTesting.kt b/packages/SystemUI/src/com/android/systemui/util/annotations/DeprecatedSysuiVisibleForTesting.kt
new file mode 100644
index 000000000000..3d80864943da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/annotations/DeprecatedSysuiVisibleForTesting.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.util.annotations
+
+/**
+ * Given the effort in go/internal-harmful to eliminate the attempt to use Kotlin `internal` as a
+ * test-visibility marker, we are centrally moving these APIs to public, marked both with
+ * [VisibleForTesting] and this annotation. Ideally, over time, these APIs should be replaced with
+ * explicit named testing APIs (see go/internal-harmful)
+ */
+@Deprecated(
+ "Indicates an API that has been marked @VisibleForTesting, but requires further thought"
+)
+annotation class DeprecatedSysuiVisibleForTesting()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index 78a4fbecabe8..a530dda1abfc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -39,6 +39,7 @@ import com.android.systemui.shade.NotificationPanelView
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.whenever
+import com.google.android.msdl.domain.MSDLPlayer
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
@@ -59,6 +60,7 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() {
private lateinit var featureFlags: FakeFeatureFlags
@Mock private lateinit var falsingManager: FalsingManager
@Mock private lateinit var deviceEntryIconViewModel: DeviceEntryIconViewModel
+ @Mock private lateinit var msdlPlayer: MSDLPlayer
private lateinit var underTest: DefaultDeviceEntrySection
@Before
@@ -81,6 +83,7 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() {
{ mock(DeviceEntryBackgroundViewModel::class.java) },
{ falsingManager },
{ mock(VibratorHelper::class.java) },
+ { msdlPlayer },
logcatLogBuffer(),
logcatLogBuffer("blueprints"),
)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 2c800bd87ef5..a515c3f6ed6e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -171,6 +171,8 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
private ConversationIconFactory mIconFactory;
@Mock
private Notification.BubbleMetadata mBubbleMetadata;
+ @Mock
+ private View.OnClickListener mCloseListener;
private Handler mTestHandler;
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -298,7 +300,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
- mShadeController);
+ mShadeController, true, mCloseListener);
}
@Test
@@ -402,7 +404,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
- mShadeController);
+ mShadeController, true, null);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(VISIBLE, nameView.getVisibility());
assertTrue(nameView.getText().toString().contains("Proxied"));
@@ -442,7 +444,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
- mShadeController);
+ mShadeController, true, null);
final View feedback = mNotificationInfo.findViewById(R.id.feedback);
assertEquals(VISIBLE, feedback.getVisibility());
@@ -484,7 +486,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
- mShadeController);
+ mShadeController, true, null);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
settingsButton.performClick();
@@ -524,7 +526,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
false,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
- mShadeController);
+ mShadeController, true, null);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertTrue(settingsButton.getVisibility() != View.VISIBLE);
}
@@ -601,7 +603,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
- mShadeController);
+ mShadeController, true, null);
assertThat(((TextView) mNotificationInfo.findViewById(R.id.priority_summary)).getText())
.isEqualTo(mContext.getString(
R.string.notification_channel_summary_priority_dnd));
@@ -633,7 +635,7 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
true,
mTestHandler,
mTestHandler, null, Optional.of(mBubblesManager),
- mShadeController);
+ mShadeController, true, null);
assertThat(((TextView) mNotificationInfo.findViewById(R.id.priority_summary)).getText())
.isEqualTo(mContext.getString(
R.string.notification_channel_summary_priority_baseline));
@@ -1018,4 +1020,19 @@ public class NotificationConversationInfoTest extends SysuiTestCase {
// THEN the user is not presented with the People Tile pinning request
verify(mPeopleSpaceWidgetManager, never()).requestPinAppWidget(eq(mShortcutInfo), any());
}
+
+
+ @Test
+ public void testDismiss() throws Exception {
+ doStandardBind();
+
+ View dismiss = mNotificationInfo.findViewById(R.id.inline_dismiss);
+ dismiss.performClick();
+ mTestableLooper.processAllMessages();
+
+ // Verify action performed on button click
+ verify(mCloseListener).onClick(any());
+
+ }
+
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 574b2c010a37..fc33db6e57e6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -445,27 +445,30 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
@Test
@EnableFlags(ShadeWindowGoesAround.FLAG_NAME)
- fun onTouch_actionDown_propagatesToDisplayPolicy() {
+ fun onInterceptTouchEvent_actionDown_propagatesToDisplayPolicy() {
val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
- controller.onTouch(event)
+
+ view.onInterceptTouchEvent(event)
verify(statusBarTouchShadeDisplayPolicy).onStatusBarTouched(eq(event), any())
}
@Test
@EnableFlags(ShadeWindowGoesAround.FLAG_NAME)
- fun onTouch_actionUp_notPropagatesToDisplayPolicy() {
+ fun onInterceptTouchEvent_actionUp_notPropagatesToDisplayPolicy() {
val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)
- controller.onTouch(event)
+
+ view.onInterceptTouchEvent(event)
verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(any(), any())
}
@Test
@DisableFlags(ShadeWindowGoesAround.FLAG_NAME)
- fun onTouch_shadeWindowGoesAroundDisabled_notPropagatesToDisplayPolicy() {
+ fun onInterceptTouchEvent_shadeWindowGoesAroundDisabled_notPropagatesToDisplayPolicy() {
val event = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
- controller.onTouch(event)
+
+ view.onInterceptTouchEvent(event)
verify(statusBarTouchShadeDisplayPolicy, never()).onStatusBarTouched(eq(event), any())
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 4a0445d5543a..5b29bffc5148 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -67,6 +67,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.Dependency;
+import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.AnimatorTestRule;
import com.android.systemui.flags.FakeFeatureFlags;
@@ -409,9 +410,14 @@ public class RemoteInputViewTest extends SysuiTestCase {
// fast forward to end of animation
mAnimatorTestRule.advanceTimeBy(1);
- // assert that fadeOutView's alpha is reset to 1f after the animation (hidden behind
- // RemoteInputView)
- assertEquals(1f, fadeOutView.getAlpha());
+ if (Flags.notificationRowTransparency()) {
+ // With transparent rows, fadeOutView should be hidden after the animation.
+ assertEquals(0f, fadeOutView.getAlpha());
+ } else {
+ // assert that fadeOutView's alpha is reset to 1f after the animation (hidden behind
+ // RemoteInputView)
+ assertEquals(1f, fadeOutView.getAlpha());
+ }
assertFalse(view.isAnimatingAppearance());
assertEquals(View.VISIBLE, view.getVisibility());
assertEquals(1f, view.getAlpha());
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt
index 015d4ddcd54e..19c42260d957 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/AuthRippleInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.deviceentry.domain.interactor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
val Kosmos.authRippleInteractor by
@@ -23,5 +24,6 @@ val Kosmos.authRippleInteractor by
AuthRippleInteractor(
deviceEntrySourceInteractor = deviceEntrySourceInteractor,
deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
+ keyguardInteractor = keyguardInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
index cd4b09c5267a..91c3da4f616f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt
@@ -21,6 +21,7 @@ import com.android.systemui.biometrics.data.repository.fingerprintPropertyReposi
import com.android.systemui.dump.dumpManager
import com.android.systemui.keyevent.domain.interactor.keyEventInteractor
import com.android.systemui.keyguard.data.repository.biometricSettingsRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.util.time.systemClock
@@ -36,6 +37,7 @@ val Kosmos.deviceEntryHapticsInteractor by
keyEventInteractor = keyEventInteractor,
logger = biometricUnlockLogger,
powerInteractor = powerInteractor,
+ keyguardInteractor = keyguardInteractor,
systemClock = systemClock,
dumpManager = dumpManager,
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
index c4542c4e709b..00b26c944b90 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/PromotedNotificationContentExtractorKosmos.kt
@@ -19,7 +19,7 @@ package com.android.systemui.statusbar.notification.promoted
import android.app.Notification
import android.content.applicationContext
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_NONE
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.REDACTION_TYPE_PUBLIC
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.row.RowImageInflater
import com.android.systemui.statusbar.notification.row.shared.skeletonImageTransform
@@ -40,7 +40,7 @@ fun Kosmos.setPromotedContent(entry: NotificationEntry) {
promotedNotificationContentExtractor.extractContent(
entry,
Notification.Builder.recoverBuilder(applicationContext, entry.sbn.notification),
- REDACTION_TYPE_NONE,
+ REDACTION_TYPE_PUBLIC,
RowImageInflater.newInstance(previousIndex = null, reinflating = false)
.useForContentModel(),
)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractorKosmos.kt
index fcd484353011..ea459a95728a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/promoted/domain/interactor/AODPromotedNotificationInteractorKosmos.kt
@@ -17,12 +17,16 @@
package com.android.systemui.statusbar.notification.promoted.domain.interactor
import com.android.systemui.dump.dumpManager
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.policy.domain.interactor.sensitiveNotificationProtectionInteractor
val Kosmos.aodPromotedNotificationInteractor by
Kosmos.Fixture {
AODPromotedNotificationInteractor(
promotedNotificationsInteractor = promotedNotificationsInteractor,
+ keyguardInteractor = keyguardInteractor,
+ sensitiveNotificationProtectionInteractor = sensitiveNotificationProtectionInteractor,
dumpManager = dumpManager,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/SensitiveNotificationProtectionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/SensitiveNotificationProtectionInteractorKosmos.kt
new file mode 100644
index 000000000000..ba4410b51b75
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/domain/interactor/SensitiveNotificationProtectionInteractorKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.statusbar.policy.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.statusbar.policy.sensitiveNotificationProtectionController
+
+var Kosmos.sensitiveNotificationProtectionInteractor: SensitiveNotificationProtectionInteractor by
+ Kosmos.Fixture {
+ SensitiveNotificationProtectionInteractor(sensitiveNotificationProtectionController)
+ }