summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SettingsLib/BannerMessagePreference/Android.bp1
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_resolved_banner_avd.xml157
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml137
-rw-r--r--packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml8
-rw-r--r--packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java222
-rw-r--r--packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java16
-rw-r--r--packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/ResolutionAnimator.kt196
-rw-r--r--packages/SettingsLib/ButtonPreference/Android.bp1
8 files changed, 626 insertions, 112 deletions
diff --git a/packages/SettingsLib/BannerMessagePreference/Android.bp b/packages/SettingsLib/BannerMessagePreference/Android.bp
index 182daeb45d7c..203a3bf64910 100644
--- a/packages/SettingsLib/BannerMessagePreference/Android.bp
+++ b/packages/SettingsLib/BannerMessagePreference/Android.bp
@@ -31,5 +31,6 @@ android_library {
apex_available: [
"//apex_available:platform",
"com.android.healthfitness",
+ "com.android.permission",
],
}
diff --git a/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_resolved_banner_avd.xml b/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_resolved_banner_avd.xml
new file mode 100644
index 000000000000..c999de7d99ea
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/res/drawable-v35/settingslib_resolved_banner_avd.xml
@@ -0,0 +1,157 @@
+<!--
+ ~ 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.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="112dp"
+ android:height="112dp"
+ android:viewportHeight="112"
+ android:viewportWidth="112">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_1_G"
+ android:translateX="56.5"
+ android:translateY="56.625">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="0"
+ android:fillColor="?android:attr/textColorPrimary"
+ android:fillType="nonZero"
+ android:pathData=" M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:trimPathEnd="1"
+ android:trimPathOffset="0"
+ android:trimPathStart="0" />
+ </group>
+ <group
+ android:name="_R_G_L_0_G"
+ android:rotation="-90"
+ android:translateX="56"
+ android:translateY="56">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:pathData=" M53.5 0 C53.5,-29.53 29.53,-53.5 0,-53.5 C0,-53.5 0,-53.5 0,-53.5 C-29.53,-53.5 -53.5,-29.53 -53.5,0 C-53.5,0 -53.5,0 -53.5,0 C-53.5,29.53 -29.53,53.5 0,53.5 C0,53.5 0,53.5 0,53.5 C29.53,53.5 53.5,29.53 53.5,0 C53.5,0 53.5,0 53.5,0c "
+ android:strokeAlpha="1"
+ android:strokeColor="?android:attr/textColorTertiary"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="5"
+ android:trimPathEnd="0"
+ android:trimPathOffset="0"
+ android:trimPathStart="0" />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="400"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="fillAlpha"
+ android:startOffset="400"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="400"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:valueTo="M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="pathData"
+ android:startOffset="400"
+ android:valueFrom="M-14.75 -0.59 C-14.75,-0.59 -15.32,-1.16 -15.32,-1.16 C-15.32,-1.16 -14.67,-0.73 -14.67,-0.73 C-14.67,-0.73 -14.39,-0.44 -14.39,-0.44 C-14.39,-0.44 -17.32,2.5 -17.32,2.5 C-17.32,2.5 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:valueTo="M-14.75 -0.59 C-14.75,-0.59 -6.41,7.75 -6.41,7.75 C-6.41,7.75 -6.3,7.65 -6.3,7.65 C-6.3,7.65 -3.48,10.47 -3.48,10.47 C-3.48,10.47 -6.41,13.41 -6.41,13.41 C-6.41,13.41 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="pathData"
+ android:startOffset="483"
+ android:valueFrom="M-14.75 -0.59 C-14.75,-0.59 -6.41,7.75 -6.41,7.75 C-6.41,7.75 -6.3,7.65 -6.3,7.65 C-6.3,7.65 -3.48,10.47 -3.48,10.47 C-3.48,10.47 -6.41,13.41 -6.41,13.41 C-6.41,13.41 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:valueTo="M-14.75 -0.59 C-14.75,-0.59 -6.41,7.75 -6.41,7.75 C-6.41,7.75 14.77,-13.41 14.77,-13.41 C14.77,-13.41 17.59,-10.59 17.59,-10.59 C17.59,-10.59 -6.41,13.41 -6.41,13.41 C-6.41,13.41 -17.59,2.23 -17.59,2.23 C-17.59,2.23 -14.75,-0.59 -14.75,-0.59c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="500"
+ android:propertyName="trimPathEnd"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.2,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="667"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml b/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml
index b10ef6e77ec7..c448a2d434f8 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/layout-v35/settingslib_expressive_banner_message.xml
@@ -28,81 +28,104 @@
android:orientation="vertical"
style="@style/Banner.Preference.SettingsLib.Expressive">
- <RelativeLayout
- android:id="@+id/top_row"
+ <FrameLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal">
+ android:layout_height="wrap_content">
<LinearLayout
+ android:id="@+id/banner_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_alignParentStart="true"
- android:layout_marginEnd="@dimen/settingslib_expressive_space_medium4"
android:orientation="vertical">
- <TextView
- android:id="@+id/banner_header"
+
+ <RelativeLayout
+ android:id="@+id/top_row"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@style/Banner.Header.SettingsLib.Expressive"/>
+ android:orientation="horizontal">
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
-
- <ImageView
- android:id="@+id/banner_icon"
- android:layout_width="@dimen/settingslib_expressive_space_small3"
- android:layout_height="@dimen/settingslib_expressive_space_small3"
- android:layout_gravity="center_vertical"
- android:importantForAccessibility="no"
- android:scaleType="fitCenter" />
-
- <TextView
- android:id="@+id/banner_title"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@style/Banner.Title.SettingsLib.Expressive" />
- </LinearLayout>
+ android:layout_alignParentStart="true"
+ android:layout_marginEnd="@dimen/settingslib_expressive_space_medium4"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/banner_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Header.SettingsLib.Expressive"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <ImageView
+ android:id="@+id/banner_icon"
+ android:layout_width="@dimen/settingslib_expressive_space_small3"
+ android:layout_height="@dimen/settingslib_expressive_space_small3"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="@dimen/settingslib_expressive_space_extrasmall4"
+ android:importantForAccessibility="no"
+ android:scaleType="fitCenter" />
+
+ <TextView
+ android:id="@+id/banner_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Title.SettingsLib.Expressive" />
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/banner_subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Subtitle.SettingsLib.Expressive"/>
+ </LinearLayout>
+
+ <ImageButton
+ android:id="@+id/banner_dismiss_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/accessibility_banner_message_dismiss"
+ style="@style/Banner.Dismiss.SettingsLib.Expressive" />
+ </RelativeLayout>
<TextView
- android:id="@+id/banner_subtitle"
+ android:id="@+id/banner_summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/Banner.Summary.SettingsLib.Expressive"/>
+
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- style="@style/Banner.Subtitle.SettingsLib.Expressive"/>
+ android:id="@+id/banner_buttons_frame"
+ android:paddingTop="@dimen/settingslib_expressive_space_extrasmall6"
+ android:orientation="horizontal">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/banner_negative_btn"
+ android:layout_weight="1"
+ style="@style/Banner.NegativeButton.SettingsLib.Expressive"/>
+ <Space
+ android:id="@+id/banner_button_space"
+ android:layout_width="@dimen/settingslib_expressive_space_extrasmall4"
+ android:layout_height="@dimen/settingslib_expressive_space_small1"/>
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/banner_positive_btn"
+ android:layout_weight="1"
+ style="@style/Banner.PositiveButton.SettingsLib.Expressive"/>
+ </LinearLayout>
</LinearLayout>
- <ImageButton
- android:id="@+id/banner_dismiss_btn"
+ <TextView
+ android:id="@+id/resolved_banner_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- style="@style/Banner.Dismiss.SettingsLib.Expressive" />
- </RelativeLayout>
-
- <TextView
- android:id="@+id/banner_summary"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- style="@style/Banner.Summary.SettingsLib.Expressive"/>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:id="@+id/banner_buttons_frame"
- android:paddingTop="@dimen/settingslib_expressive_space_extrasmall6"
- android:orientation="horizontal">
-
- <com.google.android.material.button.MaterialButton
- android:id="@+id/banner_negative_btn"
- android:layout_weight="1"
- style="@style/Banner.NegativeButton.SettingsLib.Expressive"/>
- <Space
- android:layout_width="@dimen/settingslib_expressive_space_extrasmall4"
- android:layout_height="@dimen/settingslib_expressive_space_small1"/>
- <com.google.android.material.button.MaterialButton
- android:id="@+id/banner_positive_btn"
- android:layout_weight="1"
- style="@style/Banner.PositiveButton.SettingsLib.Expressive"/>
- </LinearLayout>
+ android:drawableTop="@drawable/settingslib_resolved_banner_avd"
+ android:visibility="gone"
+ style="@style/Banner.ResolvedText.SettingsLib.Expressive"/>
+ </FrameLayout>
</com.android.settingslib.widget.BannerMessageView>
</LinearLayout> \ No newline at end of file
diff --git a/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml b/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml
index b864311bf9b7..09e07ccef683 100644
--- a/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml
+++ b/packages/SettingsLib/BannerMessagePreference/res/values-v35/styles_expressive.xml
@@ -33,7 +33,6 @@
<style name="Banner.Title.SettingsLib.Expressive"
parent="">
<item name="android:layout_gravity">start</item>
- <item name="android:layout_marginLeft">@dimen/settingslib_expressive_space_extrasmall4</item>
<item name="android:textAlignment">viewStart</item>
<item name="android:textAppearance">@style/TextAppearance.SettingsLib.TitleLarge.Emphasized</item>
<item name="android:textColor">?android:attr/textColorPrimary</item>
@@ -73,4 +72,11 @@
parent="@style/SettingsLibButtonStyle.Expressive.Outline.Extra">
<item name="materialSizeOverlay">@style/SizeOverlay.Material3Expressive.Button.Small</item>
</style>
+
+ <style name="Banner.ResolvedText.SettingsLib.Expressive" parent="">
+ <item name="android:layout_gravity">center</item>
+ <item name="android:drawablePadding">@dimen/settingslib_expressive_space_small1</item>
+ <item name="android:textAppearance">@style/TextAppearance.SettingsLib.BodyMedium</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ </style>
</resources>
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 c82829d6ccea..c90a76a39510 100644
--- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java
@@ -35,6 +35,8 @@ import android.widget.TextView;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
import androidx.preference.Preference;
@@ -43,6 +45,9 @@ import androidx.preference.PreferenceViewHolder;
import com.android.settingslib.widget.preference.banner.R;
import com.google.android.material.button.MaterialButton;
+
+import java.util.Objects;
+
/**
* Banner message is a banner displaying important information (permission request, page error etc),
* and provide actions for user to address. It requires a user action to be dismissed.
@@ -67,13 +72,15 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
R.color.banner_accent_attention_normal,
R.color.settingslib_banner_button_background_normal);
- // Corresponds to the enum valye of R.attr.attentionLevel
+ // 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;
- AttentionLevel(int attrValue, @ColorRes int backgroundColorResId,
+ AttentionLevel(
+ int attrValue,
+ @ColorRes int backgroundColorResId,
@ColorRes int accentColorResId,
@ColorRes int buttonBackgroundColorResId) {
mAttrValue = attrValue;
@@ -115,33 +122,38 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
new BannerMessagePreference.DismissButtonInfo();
// Default attention level is High.
- private AttentionLevel mAttentionLevel = AttentionLevel.HIGH;
- private CharSequence mSubtitle;
- private CharSequence mHeader;
+ @NonNull private AttentionLevel mAttentionLevel = AttentionLevel.HIGH;
+ @Nullable private CharSequence mSubtitle;
+ @Nullable private CharSequence mHeader;
private int mButtonOrientation;
+ @Nullable private ResolutionAnimator.Data mResolutionData;
- public BannerMessagePreference(Context context) {
+ public BannerMessagePreference(@NonNull Context context) {
super(context);
init(context, null /* attrs */);
}
- public BannerMessagePreference(Context context, AttributeSet attrs) {
+ public BannerMessagePreference(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
- public BannerMessagePreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ public BannerMessagePreference(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
- public BannerMessagePreference(Context context, AttributeSet attrs, int defStyleAttr,
+ public BannerMessagePreference(
+ @NonNull Context context,
+ @Nullable AttributeSet attrs,
+ int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
- private void init(Context context, AttributeSet attrs) {
+ private void init(@NonNull Context context, @Nullable AttributeSet attrs) {
setSelectable(false);
int resId = SettingsThemeHelper.isExpressiveTheme(context)
? R.layout.settingslib_expressive_banner_message
@@ -166,7 +178,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
@Override
- public void onBindViewHolder(PreferenceViewHolder holder) {
+ public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final Context context = getContext();
@@ -193,14 +205,20 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
final ImageView iconView = (ImageView) holder.findViewById(R.id.banner_icon);
if (iconView != null) {
Drawable icon = getIcon();
- iconView.setImageDrawable(
- icon == null
- ? getContext().getDrawable(R.drawable.ic_warning)
- : icon);
- if (mAttentionLevel != AttentionLevel.NORMAL
- && !SettingsThemeHelper.isExpressiveTheme(context)) {
- iconView.setColorFilter(
- new PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN));
+
+ if (icon == null && SettingsThemeHelper.isExpressiveTheme(context)) {
+ iconView.setVisibility(View.GONE);
+ } else {
+ iconView.setVisibility(View.VISIBLE);
+ iconView.setImageDrawable(
+ icon == null
+ ? getContext().getDrawable(R.drawable.ic_warning)
+ : icon);
+ if (mAttentionLevel != AttentionLevel.NORMAL
+ && !SettingsThemeHelper.isExpressiveTheme(context)) {
+ iconView.setColorFilter(
+ new PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN));
+ }
}
}
@@ -233,8 +251,10 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
mDismissButtonInfo.setUpButton();
final TextView subtitleView = (TextView) holder.findViewById(R.id.banner_subtitle);
- subtitleView.setText(mSubtitle);
- subtitleView.setVisibility(mSubtitle == null ? View.GONE : View.VISIBLE);
+ if (subtitleView != null) {
+ subtitleView.setText(mSubtitle);
+ subtitleView.setVisibility(mSubtitle == null ? View.GONE : View.VISIBLE);
+ }
TextView headerView = (TextView) holder.findViewById(R.id.banner_header);
if (headerView != null) {
@@ -268,11 +288,25 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
linearLayout.setOrientation(mButtonOrientation);
}
}
+
+ View buttonSpace = holder.findViewById(R.id.banner_button_space);
+ if (buttonSpace != null) {
+ if (mPositiveButtonInfo.shouldBeVisible() && mNegativeButtonInfo.shouldBeVisible()) {
+ buttonSpace.setVisibility(View.VISIBLE);
+ } else {
+ buttonSpace.setVisibility(View.GONE);
+ }
+ }
+
+ if (mResolutionData != null) {
+ new ResolutionAnimator(mResolutionData, holder).startResolutionAnimation();
+ }
}
/**
- * Set the visibility state of positive button.
+ * Sets the visibility state of the positive button.
*/
+ @NonNull
public BannerMessagePreference setPositiveButtonVisible(boolean isVisible) {
if (isVisible != mPositiveButtonInfo.mIsVisible) {
mPositiveButtonInfo.mIsVisible = isVisible;
@@ -282,8 +316,9 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
- * Set the visibility state of negative button.
+ * Sets the visibility state of the negative button.
*/
+ @NonNull
public BannerMessagePreference setNegativeButtonVisible(boolean isVisible) {
if (isVisible != mNegativeButtonInfo.mIsVisible) {
mNegativeButtonInfo.mIsVisible = isVisible;
@@ -293,9 +328,10 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
- * Set the visibility state of dismiss button.
+ * Sets the visibility state of the dismiss button.
*/
@RequiresApi(Build.VERSION_CODES.S)
+ @NonNull
public BannerMessagePreference setDismissButtonVisible(boolean isVisible) {
if (isVisible != mDismissButtonInfo.mIsVisible) {
mDismissButtonInfo.mIsVisible = isVisible;
@@ -305,10 +341,35 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
- * Register a callback to be invoked when positive button is clicked.
+ * Sets the enabled state of the positive button.
+ */
+ @NonNull
+ public BannerMessagePreference setPositiveButtonEnabled(boolean isEnabled) {
+ if (isEnabled != mPositiveButtonInfo.mIsEnabled) {
+ mPositiveButtonInfo.mIsEnabled = isEnabled;
+ notifyChanged();
+ }
+ return this;
+ }
+
+ /**
+ * Sets the enabled state of the negative button.
+ */
+ @NonNull
+ public BannerMessagePreference setNegativeButtonEnabled(boolean isEnabled) {
+ if (isEnabled != mNegativeButtonInfo.mIsEnabled) {
+ mNegativeButtonInfo.mIsEnabled = isEnabled;
+ notifyChanged();
+ }
+ return this;
+ }
+
+ /**
+ * Registers a callback to be invoked when positive button is clicked.
*/
+ @NonNull
public BannerMessagePreference setPositiveButtonOnClickListener(
- View.OnClickListener listener) {
+ @Nullable View.OnClickListener listener) {
if (listener != mPositiveButtonInfo.mListener) {
mPositiveButtonInfo.mListener = listener;
notifyChanged();
@@ -317,10 +378,11 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
- * Register a callback to be invoked when negative button is clicked.
+ * Registers a callback to be invoked when negative button is clicked.
*/
+ @NonNull
public BannerMessagePreference setNegativeButtonOnClickListener(
- View.OnClickListener listener) {
+ @Nullable View.OnClickListener listener) {
if (listener != mNegativeButtonInfo.mListener) {
mNegativeButtonInfo.mListener = listener;
notifyChanged();
@@ -329,11 +391,12 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
- * Register a callback to be invoked when the dismiss button is clicked.
+ * Registers a callback to be invoked when the dismiss button is clicked.
*/
@RequiresApi(Build.VERSION_CODES.S)
+ @NonNull
public BannerMessagePreference setDismissButtonOnClickListener(
- View.OnClickListener listener) {
+ @Nullable View.OnClickListener listener) {
if (listener != mDismissButtonInfo.mListener) {
mDismissButtonInfo.mListener = listener;
notifyChanged();
@@ -344,6 +407,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the text to be displayed in positive button.
*/
+ @NonNull
public BannerMessagePreference setPositiveButtonText(@StringRes int textResId) {
return setPositiveButtonText(getContext().getString(textResId));
}
@@ -351,7 +415,9 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the text to be displayed in positive button.
*/
- public BannerMessagePreference setPositiveButtonText(CharSequence positiveButtonText) {
+ @NonNull
+ public BannerMessagePreference setPositiveButtonText(
+ @Nullable CharSequence positiveButtonText) {
if (!TextUtils.equals(positiveButtonText, mPositiveButtonInfo.mText)) {
mPositiveButtonInfo.mText = positiveButtonText;
notifyChanged();
@@ -362,6 +428,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the text to be displayed in negative button.
*/
+ @NonNull
public BannerMessagePreference setNegativeButtonText(@StringRes int textResId) {
return setNegativeButtonText(getContext().getString(textResId));
}
@@ -369,7 +436,9 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the text to be displayed in negative button.
*/
- public BannerMessagePreference setNegativeButtonText(CharSequence negativeButtonText) {
+ @NonNull
+ public BannerMessagePreference setNegativeButtonText(
+ @Nullable CharSequence negativeButtonText) {
if (!TextUtils.equals(negativeButtonText, mNegativeButtonInfo.mText)) {
mNegativeButtonInfo.mText = negativeButtonText;
notifyChanged();
@@ -380,8 +449,12 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets button orientation.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @NonNull
public BannerMessagePreference setButtonOrientation(int orientation) {
+ if (!SettingsThemeHelper.isExpressiveTheme(getContext())) {
+ return this;
+ }
+
if (mButtonOrientation != orientation) {
mButtonOrientation = orientation;
notifyChanged();
@@ -393,6 +466,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
* Sets the subtitle.
*/
@RequiresApi(Build.VERSION_CODES.S)
+ @NonNull
public BannerMessagePreference setSubtitle(@StringRes int textResId) {
return setSubtitle(getContext().getString(textResId));
}
@@ -401,7 +475,8 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
* Sets the subtitle.
*/
@RequiresApi(Build.VERSION_CODES.S)
- public BannerMessagePreference setSubtitle(CharSequence subtitle) {
+ @NonNull
+ public BannerMessagePreference setSubtitle(@Nullable CharSequence subtitle) {
if (!TextUtils.equals(subtitle, mSubtitle)) {
mSubtitle = subtitle;
notifyChanged();
@@ -412,7 +487,7 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the header.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @NonNull
public BannerMessagePreference setHeader(@StringRes int textResId) {
return setHeader(getContext().getString(textResId));
}
@@ -420,8 +495,12 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
/**
* Sets the header.
*/
- @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
- public BannerMessagePreference setHeader(CharSequence header) {
+ @NonNull
+ public BannerMessagePreference setHeader(@Nullable CharSequence header) {
+ if (!SettingsThemeHelper.isExpressiveTheme(getContext())) {
+ return this;
+ }
+
if (!TextUtils.equals(header, mHeader)) {
mHeader = header;
notifyChanged();
@@ -430,32 +509,75 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
/**
- * Sets the attention level. This will update the color theme of the preference.
+ * Plays a resolution animation, showing the given message.
*/
- public BannerMessagePreference setAttentionLevel(AttentionLevel attentionLevel) {
- if (attentionLevel == mAttentionLevel) {
+ @NonNull
+ public BannerMessagePreference showResolutionAnimation(
+ @NonNull CharSequence resolutionMessage,
+ @NonNull ResolutionCompletedCallback onCompletionCallback) {
+ if (!SettingsThemeHelper.isExpressiveTheme(getContext())) {
return this;
}
+ ResolutionAnimator.Data resolutionData =
+ new ResolutionAnimator.Data(resolutionMessage, onCompletionCallback);
+ if (!Objects.equals(mResolutionData, resolutionData)) {
+ mResolutionData = resolutionData;
+ notifyChanged();
+ }
+ return this;
+ }
- if (attentionLevel != null) {
- mAttentionLevel = attentionLevel;
+ /**
+ * Removes the resolution animation from this preference.
+ *
+ * <p>Should be called after the resolution animation completes if this preference will be
+ * reused. Otherwise the resolution animation will be played everytime this preference is
+ * displayed.
+ */
+ @NonNull
+ public BannerMessagePreference clearResolutionAnimation() {
+ if (!SettingsThemeHelper.isExpressiveTheme(getContext())) {
+ return this;
+ }
+ if (mResolutionData != null) {
+ mResolutionData = null;
notifyChanged();
}
return this;
}
+ /**
+ * Sets the attention level. This will update the color theme of the preference.
+ */
+ @NonNull
+ public BannerMessagePreference setAttentionLevel(@NonNull AttentionLevel attentionLevel) {
+ if (attentionLevel == mAttentionLevel) {
+ return this;
+ }
+
+ mAttentionLevel = attentionLevel;
+ notifyChanged();
+ return this;
+ }
+
static class ButtonInfo {
- private Button mButton;
- private CharSequence mText;
- private View.OnClickListener mListener;
+ @Nullable private Button mButton;
+ @Nullable private CharSequence mText;
+ @Nullable private View.OnClickListener mListener;
private boolean mIsVisible = true;
+ private boolean mIsEnabled = true;
@ColorInt private int mColor;
@ColorInt private int mBackgroundColor;
- private ColorStateList mStrokeColor;
+ @Nullable private ColorStateList mStrokeColor;
void setUpButton() {
+ if (mButton == null) {
+ return;
+ }
+
mButton.setText(mText);
mButton.setOnClickListener(mListener);
+ mButton.setEnabled(mIsEnabled);
MaterialButton btn = null;
if (mButton instanceof MaterialButton) {
@@ -492,11 +614,15 @@ public class BannerMessagePreference extends Preference implements GroupSectionD
}
static class DismissButtonInfo {
- private ImageButton mButton;
- private View.OnClickListener mListener;
+ @Nullable private ImageButton mButton;
+ @Nullable private View.OnClickListener mListener;
private boolean mIsVisible = true;
void setUpButton() {
+ if (mButton == null) {
+ return;
+ }
+
mButton.setOnClickListener(mListener);
if (shouldBeVisible()) {
mButton.setVisibility(View.VISIBLE);
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java
index ff4e79ddaaa1..eabb6341c3dd 100644
--- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessageView.java
@@ -23,6 +23,7 @@ import android.view.TouchDelegate;
import android.view.View;
import android.widget.LinearLayout;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.settingslib.widget.preference.banner.R;
@@ -34,22 +35,25 @@ import com.android.settingslib.widget.preference.banner.R;
* {@link BannerMessagePreference} to a {@code PreferenceScreen}.
*/
public class BannerMessageView extends LinearLayout {
- private Rect mTouchTargetForDismissButton;
+ @Nullable private Rect mTouchTargetForDismissButton;
- public BannerMessageView(Context context) {
+ public BannerMessageView(@NonNull Context context) {
super(context);
}
- public BannerMessageView(Context context,
- @Nullable AttributeSet attrs) {
+ public BannerMessageView(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
- public BannerMessageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ public BannerMessageView(
+ @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
- public BannerMessageView(Context context, AttributeSet attrs, int defStyleAttr,
+ public BannerMessageView(
+ @NonNull Context context,
+ @Nullable AttributeSet attrs,
+ int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/ResolutionAnimator.kt b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/ResolutionAnimator.kt
new file mode 100644
index 000000000000..fbf910a42423
--- /dev/null
+++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/ResolutionAnimator.kt
@@ -0,0 +1,196 @@
+/*
+ * 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.settingslib.widget
+
+import android.graphics.drawable.Animatable2
+import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.provider.DeviceConfig
+import android.transition.Fade
+import android.transition.Transition
+import android.transition.TransitionListenerAdapter
+import android.transition.TransitionManager
+import android.transition.TransitionSet
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.LinearInterpolator
+import android.widget.TextView
+import androidx.preference.PreferenceViewHolder
+import com.android.settingslib.widget.preference.banner.R
+import java.time.Duration
+
+/** Callback to communicate when a banner message resolution animation is completed. */
+fun interface ResolutionCompletedCallback {
+ fun onCompleted()
+}
+
+internal class ResolutionAnimator(
+ private val data: Data,
+ private val preferenceViewHolder: PreferenceViewHolder,
+) {
+
+ data class Data(
+ val resolutionMessage: CharSequence,
+ val resolutionCompletedCallback: ResolutionCompletedCallback,
+ )
+
+ private val defaultBannerContent: View?
+ get() = preferenceViewHolder.findView(R.id.banner_content)
+ private val resolvedTextView: TextView?
+ get() = preferenceViewHolder.findView(R.id.resolved_banner_text)
+
+ fun startResolutionAnimation() {
+ resolvedTextView?.text = data.resolutionMessage
+ resolvedTextView?.resolutionDrawable?.reset()
+
+ val transitionSet =
+ TransitionSet()
+ .setOrdering(TransitionSet.ORDERING_SEQUENTIAL)
+ .setInterpolator(linearInterpolator)
+ .addTransition(hideIssueContentTransition)
+ .addTransition(
+ showResolvedContentTransition
+ .clone()
+ .addListener(
+ object : TransitionListenerAdapter() {
+ override fun onTransitionEnd(transition: Transition) {
+ super.onTransitionEnd(transition)
+ startIssueResolvedAnimation()
+ }
+ }
+ )
+ )
+
+ preferenceViewHolder.itemView.post {
+ TransitionManager.beginDelayedTransition(
+ preferenceViewHolder.itemView as ViewGroup,
+ transitionSet,
+ )
+
+ defaultBannerContent?.visibility = View.INVISIBLE
+ resolvedTextView?.visibility = View.VISIBLE
+ }
+
+ preferenceViewHolder.itemView.addOnAttachStateChangeListener(
+ object : View.OnAttachStateChangeListener {
+ override fun onViewAttachedToWindow(v: View) {}
+
+ override fun onViewDetachedFromWindow(v: View) {
+ v.removeOnAttachStateChangeListener(this)
+ cancelAnimationsAndFinish()
+ }
+ }
+ )
+ }
+
+ private fun startIssueResolvedAnimation() {
+ val animatedDrawable = resolvedTextView?.resolutionDrawable
+
+ if (animatedDrawable == null) {
+ hideResolvedUiAndFinish()
+ return
+ }
+
+ animatedDrawable.apply {
+ clearAnimationCallbacks()
+ registerAnimationCallback(
+ object : Animatable2.AnimationCallback() {
+ override fun onAnimationEnd(drawable: Drawable) {
+ super.onAnimationEnd(drawable)
+ hideResolvedUiAndFinish()
+ }
+ }
+ )
+ start()
+ }
+ }
+
+ private fun hideResolvedUiAndFinish() {
+ val hideTransition =
+ hideResolvedContentTransition
+ .clone()
+ .setInterpolator(linearInterpolator)
+ .addListener(
+ object : TransitionListenerAdapter() {
+ override fun onTransitionEnd(transition: Transition) {
+ super.onTransitionEnd(transition)
+ data.resolutionCompletedCallback.onCompleted()
+ }
+ }
+ )
+ TransitionManager.beginDelayedTransition(
+ preferenceViewHolder.itemView as ViewGroup,
+ hideTransition,
+ )
+ resolvedTextView?.visibility = View.GONE
+ }
+
+ private fun cancelAnimationsAndFinish() {
+ TransitionManager.endTransitions(preferenceViewHolder.itemView as ViewGroup)
+
+ resolvedTextView?.visibility = View.GONE
+
+ val animatedDrawable = resolvedTextView?.resolutionDrawable
+ animatedDrawable?.clearAnimationCallbacks()
+ animatedDrawable?.stop()
+
+ data.resolutionCompletedCallback.onCompleted()
+ }
+
+ private companion object {
+ private val linearInterpolator = LinearInterpolator()
+
+ private val HIDE_ISSUE_CONTENT_TRANSITION_DURATION = Duration.ofMillis(333)
+ private val hideIssueContentTransition =
+ Fade(Fade.OUT).setDuration(HIDE_ISSUE_CONTENT_TRANSITION_DURATION.toMillis())
+
+ private val SHOW_RESOLVED_CONTENT_TRANSITION_DELAY = Duration.ofMillis(133)
+ private val SHOW_RESOLVED_CONTENT_TRANSITION_DURATION = Duration.ofMillis(250)
+ private val showResolvedContentTransition =
+ Fade(Fade.IN)
+ .setStartDelay(SHOW_RESOLVED_CONTENT_TRANSITION_DELAY.toMillis())
+ .setDuration(SHOW_RESOLVED_CONTENT_TRANSITION_DURATION.toMillis())
+
+ private val hideResolvedContentTransitionDelay
+ get() =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ Duration.ofMillis(
+ DeviceConfig.getLong(
+ "settings_ui",
+ "banner_message_pref_hide_resolved_content_delay_millis",
+ 400,
+ )
+ )
+ } else {
+ Duration.ofMillis(400)
+ }
+
+ private val HIDE_RESOLVED_UI_TRANSITION_DURATION = Duration.ofMillis(167)
+ private val hideResolvedContentTransition
+ get() =
+ Fade(Fade.OUT)
+ .setStartDelay(hideResolvedContentTransitionDelay.toMillis())
+ .setDuration(HIDE_RESOLVED_UI_TRANSITION_DURATION.toMillis())
+
+ inline fun <reified T : View> PreferenceViewHolder.findView(id: Int): T? =
+ findViewById(id) as? T
+
+ val TextView.resolutionDrawable: AnimatedVectorDrawable?
+ get() = compoundDrawables.find { it != null } as? AnimatedVectorDrawable
+ }
+}
diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp
index a377f312ffbf..c8375a992d30 100644
--- a/packages/SettingsLib/ButtonPreference/Android.bp
+++ b/packages/SettingsLib/ButtonPreference/Android.bp
@@ -30,5 +30,6 @@ android_library {
apex_available: [
"//apex_available:platform",
"com.android.healthfitness",
+ "com.android.permission",
],
}