Move RadioButtonPreference from Settings to SettingsLib

Bug: 138620011
Test: build, make RunSettingsLibRoboTests
Change-Id: I49fc53503eb5bb3cdd6f2bae66e5d0b7808e5d3d
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index b532621..7760e0e 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -23,6 +23,7 @@
         "SettingsLibBarChartPreference",
         "SettingsLibProgressBar",
         "SettingsLibAdaptiveIcon",
+        "SettingsLibRadioButtonPreference",
     ],
 
     // ANDROIDMK TRANSLATION ERROR: unsupported assignment to LOCAL_SHARED_JAVA_LIBRARIES
diff --git a/packages/SettingsLib/AppPreference/Android.bp b/packages/SettingsLib/AppPreference/Android.bp
index b56181d..b07176a 100644
--- a/packages/SettingsLib/AppPreference/Android.bp
+++ b/packages/SettingsLib/AppPreference/Android.bp
@@ -4,9 +4,10 @@
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
 
-    libs: [
+    static_libs: [
         "androidx.annotation_annotation",
         "androidx.preference_preference",
+        "SettingsLibSettingsTheme",
     ],
     sdk_version: "system_current",
     min_sdk_version: "21",
diff --git a/packages/SettingsLib/EntityHeaderWidgets/Android.bp b/packages/SettingsLib/EntityHeaderWidgets/Android.bp
index 3ca4ecd..280848a 100644
--- a/packages/SettingsLib/EntityHeaderWidgets/Android.bp
+++ b/packages/SettingsLib/EntityHeaderWidgets/Android.bp
@@ -6,7 +6,7 @@
 
     static_libs: [
           "androidx.annotation_annotation",
-          "SettingsLibAppPreference"
+          "SettingsLibSettingsTheme"
     ],
 
     sdk_version: "system_current",
diff --git a/packages/SettingsLib/RadioButtonPreference/Android.bp b/packages/SettingsLib/RadioButtonPreference/Android.bp
new file mode 100644
index 0000000..136d6da
--- /dev/null
+++ b/packages/SettingsLib/RadioButtonPreference/Android.bp
@@ -0,0 +1,14 @@
+android_library {
+    name: "SettingsLibRadioButtonPreference",
+
+    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/AppPreference/res/values/dimens.xml b/packages/SettingsLib/RadioButtonPreference/AndroidManifest.xml
similarity index 70%
copy from packages/SettingsLib/AppPreference/res/values/dimens.xml
copy to packages/SettingsLib/RadioButtonPreference/AndroidManifest.xml
index e2a7a19..fda7fde 100644
--- a/packages/SettingsLib/AppPreference/res/values/dimens.xml
+++ b/packages/SettingsLib/RadioButtonPreference/AndroidManifest.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2018 The Android Open Source Project
+  Copyright (C) 2019 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.
@@ -13,8 +13,11 @@
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
-  -->
+-->
 
-<resources>
-    <dimen name="secondary_app_icon_size">32dp</dimen>
-</resources>
\ No newline at end of file
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.settingslib.widget">
+
+    <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml b/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml
new file mode 100644
index 0000000..dcb014d
--- /dev/null
+++ b/packages/SettingsLib/RadioButtonPreference/res/layout/preference_radio.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?android:attr/selectableItemBackground"
+    android:gravity="center_vertical"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+    <LinearLayout
+        android:id="@android:id/widget_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:gravity="center"
+        android:minWidth="56dp"
+        android:layout_marginEnd="16dp"
+        android:orientation="vertical"/>
+
+    <LinearLayout
+        android:id="@+id/icon_frame"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:gravity="center_vertical"
+        android:minWidth="32dp"
+        android:orientation="horizontal"
+        android:layout_marginEnd="16dp"
+        android:paddingTop="4dp"
+        android:paddingBottom="4dp">
+        <androidx.preference.internal.PreferenceImageView
+            android:id="@android:id/icon"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            settings:maxWidth="@dimen/secondary_app_icon_size"
+            settings:maxHeight="@dimen/secondary_app_icon_size"/>
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:paddingTop="16dp"
+        android:paddingBottom="16dp">
+
+        <TextView
+            android:id="@android:id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:singleLine="true"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Subhead"
+            android:ellipsize="marquee"
+            android:fadingEdge="horizontal"/>
+
+        <LinearLayout
+            android:id="@+id/summary_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:visibility="gone">
+            <TextView
+                android:id="@android:id/summary"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:textAppearance="@android:style/TextAppearance.DeviceDefault.Small"
+                android:textAlignment="viewStart"
+                android:textColor="?android:attr/textColorSecondary"/>
+
+            <TextView
+                android:id="@+id/appendix"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:textAppearance="@android:style/TextAppearance.DeviceDefault.Small"
+                android:textAlignment="viewEnd"
+                android:textColor="?android:attr/textColorSecondary"
+                android:maxLines="1"
+                android:ellipsize="end"/>
+        </LinearLayout>
+        <ProgressBar
+            android:id="@android:id/progress"
+            style="?android:attr/progressBarStyleHorizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="4dp"
+            android:max="100"
+            android:visibility="gone"/>
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/SettingsLib/AppPreference/res/values/dimens.xml b/packages/SettingsLib/RadioButtonPreference/res/layout/preference_widget_radiobutton.xml
similarity index 60%
copy from packages/SettingsLib/AppPreference/res/values/dimens.xml
copy to packages/SettingsLib/RadioButtonPreference/res/layout/preference_widget_radiobutton.xml
index e2a7a19..cb7b8eb 100644
--- a/packages/SettingsLib/AppPreference/res/values/dimens.xml
+++ b/packages/SettingsLib/RadioButtonPreference/res/layout/preference_widget_radiobutton.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2018 The Android Open Source Project
+  Copyright (C) 2019 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.
@@ -15,6 +15,11 @@
   limitations under the License.
   -->
 
-<resources>
-    <dimen name="secondary_app_icon_size">32dp</dimen>
-</resources>
\ No newline at end of file
+<RadioButton xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/checkbox"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:background="@null"
+    android:focusable="false"
+    android:clickable="false" />
diff --git a/packages/SettingsLib/RadioButtonPreference/src/com/android/settingslib/widget/RadioButtonPreference.java b/packages/SettingsLib/RadioButtonPreference/src/com/android/settingslib/widget/RadioButtonPreference.java
new file mode 100644
index 0000000..08287ac
--- /dev/null
+++ b/packages/SettingsLib/RadioButtonPreference/src/com/android/settingslib/widget/RadioButtonPreference.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2019 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.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TextView;
+
+import androidx.core.content.res.TypedArrayUtils;
+import androidx.preference.CheckBoxPreference;
+import androidx.preference.PreferenceViewHolder;
+
+/**
+ * Check box preference with check box replaced by radio button.
+ *
+ * Functionally speaking, it's actually a CheckBoxPreference. We only modified
+ * the widget to RadioButton to make it "look like" a RadioButtonPreference.
+ *
+ * In other words, there's no "RadioButtonPreferenceGroup" in this
+ * implementation. When you check one RadioButtonPreference, if you want to
+ * uncheck all the other preferences, you should do that by code yourself.
+ */
+public class RadioButtonPreference extends CheckBoxPreference {
+
+    /**
+     * Interface definition for a callback to be invoked when the preference is clicked.
+     */
+    public interface OnClickListener {
+        /**
+         * Called when a preference has been clicked.
+         *
+         * @param emiter The clicked preference
+         */
+        void onRadioButtonClicked(RadioButtonPreference emiter);
+    }
+
+    private OnClickListener mListener = null;
+    private View mAppendix;
+    private int mAppendixVisibility = -1;
+
+
+    /**
+     * Perform inflation from XML and apply a class-specific base style.
+     *
+     * @param context      The {@link Context} this is associated with, through which it can
+     *                     access the current theme, resources, {@link SharedPreferences}, etc.
+     * @param attrs        The attributes of the XML tag that is inflating the preference
+     * @param 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 RadioButtonPreference(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        setWidgetLayoutResource(R.layout.preference_widget_radiobutton);
+        setLayoutResource(R.layout.preference_radio);
+        setIconSpaceReserved(false);
+    }
+
+
+    /**
+     * Perform inflation from XML and apply a class-specific base style.
+     *
+     * @param context      The {@link Context} this is associated with, through which it can
+     *                     access the current theme, resources, {@link SharedPreferences}, etc.
+     * @param attrs        The attributes of the XML tag that is inflating the preference
+     */
+    public RadioButtonPreference(Context context, AttributeSet attrs) {
+        this(context, attrs, TypedArrayUtils.getAttr(context,
+                androidx.preference.R.attr.preferenceStyle,
+                android.R.attr.preferenceStyle));
+    }
+
+    /**
+     * Constructor to create a preference.
+     *
+     * @param context The Context this is associated with.
+     */
+    public RadioButtonPreference(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * Sets the callback to be invoked when this preference is clicked by the user.
+     *
+     * @param listener The callback to be invoked
+     */
+    public void setOnClickListener(OnClickListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Processes a click on the preference.
+     */
+    @Override
+    public void onClick() {
+        if (mListener != null) {
+            mListener.onRadioButtonClicked(this);
+        }
+    }
+
+    /**
+     * Binds the created View to the data for this preference.
+     *
+     * <p>This is a good place to grab references to custom Views in the layout and set
+     * properties on them.
+     *
+     * <p>Make sure to call through to the superclass's implementation.
+     *
+     * @param holder The ViewHolder that provides references to the views to fill in. These views
+     *               will be recycled, so you should not hold a reference to them after this method
+     *               returns.
+     */
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+
+        View summaryContainer = holder.findViewById(R.id.summary_container);
+        if (summaryContainer != null) {
+            summaryContainer.setVisibility(
+                    TextUtils.isEmpty(getSummary()) ? View.GONE : View.VISIBLE);
+            mAppendix = holder.findViewById(R.id.appendix);
+            if (mAppendix != null && mAppendixVisibility != -1) {
+                mAppendix.setVisibility(mAppendixVisibility);
+            }
+        }
+
+        TextView title = (TextView) holder.findViewById(android.R.id.title);
+        if (title != null) {
+            title.setSingleLine(false);
+            title.setMaxLines(3);
+        }
+    }
+
+    /**
+     * Set the visibility state of appendix view.
+     *
+     * @param visibility One of {@link View#VISIBLE}, {@link View#INVISIBLE}, or {@link View#GONE}.
+     */
+    public void setAppendixVisibility(int visibility) {
+        if (mAppendix != null) {
+            mAppendix.setVisibility(visibility);
+        }
+        mAppendixVisibility = visibility;
+    }
+}
diff --git a/packages/SettingsLib/SettingsTheme/Android.bp b/packages/SettingsLib/SettingsTheme/Android.bp
new file mode 100644
index 0000000..6d505bf
--- /dev/null
+++ b/packages/SettingsLib/SettingsTheme/Android.bp
@@ -0,0 +1,8 @@
+android_library {
+    name: "SettingsLibSettingsTheme",
+
+    resource_dirs: ["res"],
+
+    sdk_version: "system_current",
+    min_sdk_version: "21",
+}
diff --git a/packages/SettingsLib/AppPreference/res/values/dimens.xml b/packages/SettingsLib/SettingsTheme/AndroidManifest.xml
similarity index 70%
copy from packages/SettingsLib/AppPreference/res/values/dimens.xml
copy to packages/SettingsLib/SettingsTheme/AndroidManifest.xml
index e2a7a19..fda7fde 100644
--- a/packages/SettingsLib/AppPreference/res/values/dimens.xml
+++ b/packages/SettingsLib/SettingsTheme/AndroidManifest.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2018 The Android Open Source Project
+  Copyright (C) 2019 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.
@@ -13,8 +13,11 @@
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
-  -->
+-->
 
-<resources>
-    <dimen name="secondary_app_icon_size">32dp</dimen>
-</resources>
\ No newline at end of file
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.settingslib.widget">
+
+    <uses-sdk android:minSdkVersion="21" />
+
+</manifest>
diff --git a/packages/SettingsLib/AppPreference/res/values/dimens.xml b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
similarity index 92%
rename from packages/SettingsLib/AppPreference/res/values/dimens.xml
rename to packages/SettingsLib/SettingsTheme/res/values/dimens.xml
index e2a7a19..9485655 100644
--- a/packages/SettingsLib/AppPreference/res/values/dimens.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/dimens.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2018 The Android Open Source Project
+  Copyright (C) 2019 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.
@@ -17,4 +17,4 @@
 
 <resources>
     <dimen name="secondary_app_icon_size">32dp</dimen>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/RadioButtonPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/RadioButtonPreferenceTest.java
new file mode 100644
index 0000000..d58e68a
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/RadioButtonPreferenceTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019 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.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.Application;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import androidx.preference.PreferenceViewHolder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class RadioButtonPreferenceTest {
+
+    private Application mContext;
+    private RadioButtonPreference mPreference;
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mPreference = new RadioButtonPreference(mContext);
+    }
+
+    @Test
+    public void shouldHaveRadioPreferenceLayout() {
+        assertThat(mPreference.getLayoutResource()).isEqualTo(R.layout.preference_radio);
+    }
+
+    @Test
+    public void iconSpaceReservedShouldBeFalse() {
+        assertThat(mPreference.isIconSpaceReserved()).isFalse();
+    }
+
+    @Test
+    public void summary_containerShouldBeVisible() {
+        mPreference.setSummary("some summary");
+        View summaryContainer = new View(mContext);
+        View view = mock(View.class);
+        when(view.findViewById(R.id.summary_container)).thenReturn(summaryContainer);
+        PreferenceViewHolder preferenceViewHolder =
+                PreferenceViewHolder.createInstanceForTests(view);
+        mPreference.onBindViewHolder(preferenceViewHolder);
+        assertEquals(View.VISIBLE, summaryContainer.getVisibility());
+    }
+
+    @Test
+    public void emptySummary_containerShouldBeGone() {
+        mPreference.setSummary("");
+        View summaryContainer = new View(mContext);
+        View view = mock(View.class);
+        when(view.findViewById(R.id.summary_container)).thenReturn(summaryContainer);
+        PreferenceViewHolder preferenceViewHolder =
+                PreferenceViewHolder.createInstanceForTests(view);
+        mPreference.onBindViewHolder(preferenceViewHolder);
+        assertEquals(View.GONE, summaryContainer.getVisibility());
+    }
+
+    @Test
+    public void nullSummary_containerShouldBeGone() {
+        mPreference.setSummary(null);
+        View summaryContainer = new View(mContext);
+        View view = mock(View.class);
+        when(view.findViewById(R.id.summary_container)).thenReturn(summaryContainer);
+        PreferenceViewHolder preferenceViewHolder =
+                PreferenceViewHolder.createInstanceForTests(view);
+        mPreference.onBindViewHolder(preferenceViewHolder);
+        assertEquals(View.GONE, summaryContainer.getVisibility());
+    }
+
+    @Test
+    public void hideAppendix_shouldBeGone() {
+        mPreference.setAppendixVisibility(View.GONE);
+        View view = LayoutInflater.from(mContext).inflate(R.layout.preference_radio, null);
+        PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(view);
+        mPreference.onBindViewHolder(holder);
+        assertThat(holder.findViewById(R.id.appendix).getVisibility()).isEqualTo(View.GONE);
+    }
+}