diff options
Diffstat (limited to 'packages')
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 <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to stream audio and system features between your <xliff:g id="device_type" example="phone">%2$s</xliff:g> and <strong><xliff:g id="device_name" example="Chromebook">%3$s</xliff:g></strong>?</string> + <!-- Confirmation for associating an application with a companion device of VIRTUAL_DEVICE profile (type) [CHAR LIMIT=NONE] --> + <string name="title_virtual_device">Allow <strong><xliff:g id="app_name" example="Exo">%1$s</xliff:g></strong> to stream audio and system features between your <xliff:g id="device_type" example="phone">%3$s</xliff:g> and <strong><xliff:g id="device_name" example="Chromebook">%2$s</xliff:g></strong>?</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>.<br/><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>.<br/><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) + } |