diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index b266df53b18e..fcf2282160a7 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -49,6 +49,7 @@ android_library {
"SettingsLibTwoTargetPreference",
"SettingsLibSettingsTransition",
"SettingsLibActivityEmbedding",
+ "SettingsLibButtonPreference",
],
// ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES
diff --git a/packages/SettingsLib/ButtonPreference/Android.bp b/packages/SettingsLib/ButtonPreference/Android.bp
new file mode 100644
index 000000000000..39f804fa9ae5
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/Android.bp
@@ -0,0 +1,23 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+ name: "SettingsLibButtonPreference",
+
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+
+ static_libs: [
+ "androidx.preference_preference",
+ "SettingsLibSettingsTheme",
+ ],
+
+ sdk_version: "system_current",
+ min_sdk_version: "21",
+}
diff --git a/packages/SettingsLib/ButtonPreference/AndroidManifest.xml b/packages/SettingsLib/ButtonPreference/AndroidManifest.xml
new file mode 100644
index 000000000000..2d35c331cd82
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/AndroidManifest.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_background_material.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_background_material.xml
new file mode 100644
index 000000000000..51ca4ac04189
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_background_material.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_text_material.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_text_material.xml
new file mode 100644
index 000000000000..8dca4dbcd111
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_btn_colored_text_material.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_dark.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_dark.xml
new file mode 100644
index 000000000000..1e930eacffba
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_dark.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_light.xml b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_light.xml
new file mode 100644
index 000000000000..378fc166af7f
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/color/settingslib_ripple_material_light.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_btn_colored_material.xml b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_btn_colored_material.xml
new file mode 100644
index 000000000000..bb0597d1f74f
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/drawable/settingslib_btn_colored_material.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml
new file mode 100644
index 000000000000..1ff09901ffaf
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/layout/settingslib_button_layout.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
diff --git a/packages/SettingsLib/ButtonPreference/res/values-night/colors.xml b/packages/SettingsLib/ButtonPreference/res/values-night/colors.xml
new file mode 100644
index 000000000000..6be7b93a847e
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values-night/colors.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+ @color/settingslib_ripple_material_light
+
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/values-v23/styles.xml b/packages/SettingsLib/ButtonPreference/res/values-v23/styles.xml
new file mode 100644
index 000000000000..202645f0584d
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values-v23/styles.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
diff --git a/packages/SettingsLib/ButtonPreference/res/values-v28/styles.xml b/packages/SettingsLib/ButtonPreference/res/values-v28/styles.xml
new file mode 100644
index 000000000000..d8c6ac3fa471
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values-v28/styles.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
diff --git a/packages/SettingsLib/ButtonPreference/res/values-v31/styles.xml b/packages/SettingsLib/ButtonPreference/res/values-v31/styles.xml
new file mode 100644
index 000000000000..12dcbbf1a08a
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values-v31/styles.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
diff --git a/packages/SettingsLib/ButtonPreference/res/values/attrs.xml b/packages/SettingsLib/ButtonPreference/res/values/attrs.xml
new file mode 100644
index 000000000000..9a4312aa8b36
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values/attrs.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/values/colors.xml b/packages/SettingsLib/ButtonPreference/res/values/colors.xml
new file mode 100644
index 000000000000..45baeebe6feb
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values/colors.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+ @color/settingslib_ripple_material_dark
+
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/values/dimens.xml b/packages/SettingsLib/ButtonPreference/res/values/dimens.xml
new file mode 100644
index 000000000000..3d7831e80cee
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values/dimens.xml
@@ -0,0 +1,20 @@
+
+
+
+ - 0.10
+ - 0.10
+
\ No newline at end of file
diff --git a/packages/SettingsLib/ButtonPreference/res/values/styles.xml b/packages/SettingsLib/ButtonPreference/res/values/styles.xml
new file mode 100644
index 000000000000..3963732f7ae4
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/res/values/styles.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
diff --git a/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
new file mode 100644
index 000000000000..56d296709914
--- /dev/null
+++ b/packages/SettingsLib/ButtonPreference/src/com/android/settingslib/widget/ButtonPreference.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2022 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.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import androidx.annotation.GravityInt;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+/**
+ * A preference handled a button
+ */
+public class ButtonPreference extends Preference {
+
+ private static final int ICON_SIZE = 24;
+
+ private View.OnClickListener mClickListener;
+ private Button mButton;
+ private CharSequence mTitle;
+ private Drawable mIcon;
+ @GravityInt
+ private int mGravity;
+
+ /**
+ * Constructs a new LayoutPreference with the given context's theme, the supplied
+ * attribute set, and default style attribute.
+ *
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ * @param defStyleAttr An attribute in the current theme that contains a
+ * reference to a style resource that supplies default
+ * values for the view. Can be 0 to not look for
+ * defaults.
+ */
+ public ButtonPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ init(context, attrs, defStyleAttr);
+ }
+
+ /**
+ * Constructs a new LayoutPreference with the given context's theme and the supplied
+ * attribute set.
+ *
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ * @param attrs The attributes of the XML tag that is inflating the view.
+ */
+ public ButtonPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, 0 /* defStyleAttr */);
+ }
+
+ /**
+ * Constructs a new LayoutPreference with the given context's theme and a customized view.
+ *
+ * @param context The Context the view is running in, through which it can
+ * access the current theme, resources, etc.
+ */
+ public ButtonPreference(Context context) {
+ this(context, null);
+ }
+
+ private void init(Context context, AttributeSet attrs, int defStyleAttr) {
+ setLayoutResource(R.layout.settingslib_button_layout);
+
+ if (attrs != null) {
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ androidx.preference.R.styleable.Preference, defStyleAttr,
+ 0 /*defStyleRes*/);
+ mTitle = a.getText(
+ androidx.preference.R.styleable.Preference_android_title);
+ mIcon = a.getDrawable(
+ androidx.preference.R.styleable.Preference_android_icon);
+ a.recycle();
+
+ a = context.obtainStyledAttributes(attrs,
+ R.styleable.ButtonPreference, defStyleAttr,
+ 0 /*defStyleRes*/);
+ mGravity = a.getInt(R.styleable.ButtonPreference_android_gravity, Gravity.START);
+ a.recycle();
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ mButton = (Button) holder.findViewById(R.id.settingslib_button);
+ setTitle(mTitle);
+ setIcon(mIcon);
+ setGravity(mGravity);
+ setOnClickListener(mClickListener);
+
+ if (mButton != null) {
+ final boolean selectable = isSelectable();
+ mButton.setFocusable(selectable);
+ mButton.setClickable(selectable);
+
+ mButton.setEnabled(isEnabled());
+ }
+
+ holder.setDividerAllowedAbove(false);
+ holder.setDividerAllowedBelow(false);
+ }
+
+ @Override
+ public void setTitle(CharSequence title) {
+ mTitle = title;
+ if (mButton != null) {
+ mButton.setText(title);
+ }
+ }
+
+ @Override
+ public void setIcon(Drawable icon) {
+ mIcon = icon;
+ if (mButton == null || icon == null) {
+ return;
+ }
+ //get pixel from dp
+ int size = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ICON_SIZE,
+ getContext().getResources().getDisplayMetrics());
+ icon.setBounds(0, 0, size, size);
+
+ //set drawableStart
+ mButton.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null/* top */, null/* end */,
+ null/* bottom */);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ if (mButton != null) {
+ mButton.setEnabled(enabled);
+ }
+ }
+
+ /**
+ * Return Button
+ */
+ public Button getButton() {
+ return mButton;
+ }
+
+
+ /**
+ * Set a listener for button click
+ */
+ public void setOnClickListener(View.OnClickListener listener) {
+ mClickListener = listener;
+ if (mButton != null) {
+ mButton.setOnClickListener(listener);
+ }
+ }
+
+ /**
+ * Set the gravity of button
+ *
+ * @param gravity The {@link Gravity} supported CENTER_HORIZONTAL
+ * and the default value is START
+ */
+ public void setGravity(@GravityInt int gravity) {
+ if (gravity == Gravity.CENTER_HORIZONTAL || gravity == Gravity.CENTER_VERTICAL
+ || gravity == Gravity.CENTER) {
+ mGravity = Gravity.CENTER_HORIZONTAL;
+ } else {
+ mGravity = Gravity.START;
+ }
+
+ if (mButton != null) {
+ LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mButton.getLayoutParams();
+ lp.gravity = mGravity;
+ mButton.setLayoutParams(lp);
+ }
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java
new file mode 100644
index 000000000000..625b214544f1
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/ButtonPreferenceTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2022 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.LinearLayout;
+
+import androidx.preference.PreferenceViewHolder;
+import androidx.preference.R;
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.shadows.ShadowDrawable;
+
+@RunWith(RobolectricTestRunner.class)
+public class ButtonPreferenceTest {
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+ private ButtonPreference mPreference;
+ private PreferenceViewHolder mHolder;
+
+ private boolean mClickListenerCalled;
+ private final View.OnClickListener mClickListener = v -> mClickListenerCalled = true;
+
+ @Before
+ public void setUp() {
+ mClickListenerCalled = false;
+ mPreference = new ButtonPreference(mContext);
+ setUpViewHolder();
+ }
+
+ @Test
+ public void onBindViewHolder_whenTitleSet_shouldSetButtonText() {
+ final String testTitle = "Test title";
+ mPreference.setTitle(testTitle);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ assertThat(button.getText().toString()).isEqualTo(testTitle);
+ }
+
+ @Test
+ public void onBindViewHolder_whenIconSet_shouldSetIcon() {
+ mPreference.setIcon(R.drawable.settingslib_ic_cross);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ final Drawable icon = button.getCompoundDrawablesRelative()[0];
+ final ShadowDrawable shadowDrawable = shadowOf(icon);
+ assertThat(shadowDrawable.getCreatedFromResId()).isEqualTo(R.drawable.settingslib_ic_cross);
+ }
+
+ @Test
+ public void onBindViewHolder_setEnable_shouldSetButtonEnabled() {
+ mPreference.setEnabled(true);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ assertThat(button.isEnabled()).isTrue();
+ }
+
+ @Test
+ public void onBindViewHolder_setDisable_shouldSetButtonDisabled() {
+ mPreference.setEnabled(false);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ assertThat(button.isEnabled()).isFalse();
+ }
+
+ @Test
+ public void onBindViewHolder_default_shouldReturnButtonGravityStart() {
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) button.getLayoutParams();
+ assertThat(lp.gravity).isEqualTo(Gravity.START);
+ }
+
+ @Test
+ public void onBindViewHolder_setGravityStart_shouldReturnButtonGravityStart() {
+ mPreference.setGravity(Gravity.START);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) button.getLayoutParams();
+ assertThat(lp.gravity).isEqualTo(Gravity.START);
+ }
+
+ @Test
+ public void onBindViewHolder_setGravityCenter_shouldReturnButtonGravityCenterHorizontal() {
+ mPreference.setGravity(Gravity.CENTER_HORIZONTAL);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) button.getLayoutParams();
+ assertThat(lp.gravity).isEqualTo(Gravity.CENTER_HORIZONTAL);
+
+ mPreference.setGravity(Gravity.CENTER_VERTICAL);
+ mPreference.onBindViewHolder(mHolder);
+ assertThat(lp.gravity).isEqualTo(Gravity.CENTER_HORIZONTAL);
+
+ mPreference.setGravity(Gravity.CENTER);
+ mPreference.onBindViewHolder(mHolder);
+ assertThat(lp.gravity).isEqualTo(Gravity.CENTER_HORIZONTAL);
+ }
+
+ @Test
+ public void onBindViewHolder_setUnsupportedGravity_shouldReturnButtonGravityStart() {
+ mPreference.setGravity(Gravity.END);
+
+ mPreference.onBindViewHolder(mHolder);
+
+ final Button button = mPreference.getButton();
+ final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) button.getLayoutParams();
+ assertThat(lp.gravity).isEqualTo(Gravity.START);
+ }
+
+ @Test
+ public void setButtonOnClickListener_setsClickListener() {
+ mPreference.setOnClickListener(mClickListener);
+
+ mPreference.onBindViewHolder(mHolder);
+ final Button button = mPreference.getButton();
+ button.callOnClick();
+
+ assertThat(mClickListenerCalled).isTrue();
+ }
+
+ private void setUpViewHolder() {
+ final View rootView =
+ View.inflate(mContext, mPreference.getLayoutResource(), null /* parent */);
+ mHolder = PreferenceViewHolder.createInstanceForTests(rootView);
+ }
+}