Show a message when disabled switch preferences are clicked

Now preference is not disabled but view within the preferences are disabled. So the preference can still get the clicks and a message will be displayed.

Bug: 168308383
Fix: 141482424
Test: Manual, paintbooth
Change-Id: I0c46dd779a813d2e182cb4c1b416bd8d68ece081
Merged-In: I0c46dd779a813d2e182cb4c1b416bd8d68ece081
diff --git a/car-ui-lib/src/com/android/car/ui/preference/CarUiPreference.java b/car-ui-lib/src/com/android/car/ui/preference/CarUiPreference.java
index 2fbbacb..7956219 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/CarUiPreference.java
+++ b/car-ui-lib/src/com/android/car/ui/preference/CarUiPreference.java
@@ -21,27 +21,25 @@
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.View;
-import android.view.ViewGroup;
 import android.widget.Toast;
 
-import androidx.core.view.ViewCompat;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
 import com.android.car.ui.R;
+import com.android.car.ui.utils.CarUiUtils;
 
 /**
  * This class extends the base {@link Preference} class. Adds the support to add a drawable icon to
  * the preference if there is one of fragment, intent or onPreferenceClickListener set.
  */
-public class CarUiPreference extends Preference {
+public class CarUiPreference extends Preference implements IDisabledPreferenceCallback {
 
     private Context mContext;
     private boolean mShowChevron;
     private String mMessageToShowWhenDisabledPreferenceClicked;
 
     private boolean mShouldShowRippleOnDisabledPreference;
-    private boolean mEnabledAppearance = true;
     private Drawable mBackground;
     private View mPreference;
 
@@ -82,37 +80,9 @@
     public void onBindViewHolder(PreferenceViewHolder holder) {
         super.onBindViewHolder(holder);
         boolean viewEnabled = isEnabled();
-        if (viewEnabled) {
-            if (mBackground != null) {
-                ViewCompat.setBackground(holder.itemView, mBackground);
-            }
-            enableView(holder.itemView, true, false);
-        } else {
-            holder.itemView.setEnabled(true);
-            mPreference = holder.itemView;
-            if (mBackground == null) {
-                // store the original background.
-                mBackground = mPreference.getBackground();
-            }
-            if (!mShouldShowRippleOnDisabledPreference) {
-                ViewCompat.setBackground(mPreference, null);
-            } else if (mBackground != null) {
-                ViewCompat.setBackground(mPreference, mBackground);
-            }
-            enableView(holder.itemView, false, true);
-        }
-    }
-
-    private void enableView(View view, boolean enabled, boolean isRootView) {
-        if (!isRootView) {
-            view.setEnabled(enabled);
-        }
-        if (view instanceof ViewGroup) {
-            ViewGroup grp = (ViewGroup) view;
-            for (int index = 0; index < grp.getChildCount(); index++) {
-                enableView(grp.getChildAt(index), enabled, false);
-            }
-        }
+        mPreference = holder.itemView;
+        mBackground = CarUiUtils.setPreferenceViewEnabled(viewEnabled, holder.itemView, mBackground,
+                mShouldShowRippleOnDisabledPreference);
     }
 
     @Override
@@ -142,9 +112,8 @@
             super.performClick();
         } else if (mMessageToShowWhenDisabledPreferenceClicked != null
                 && !mMessageToShowWhenDisabledPreferenceClicked.isEmpty()) {
-            Toast toast = Toast.makeText(mContext, mMessageToShowWhenDisabledPreferenceClicked,
-                    Toast.LENGTH_LONG);
-            toast.show();
+            Toast.makeText(mContext, mMessageToShowWhenDisabledPreferenceClicked,
+                    Toast.LENGTH_LONG).show();
         }
     }
 
@@ -155,22 +124,14 @@
     /**
      * Sets the ripple on the disabled preference.
      */
+    @Override
     public void setShouldShowRippleOnDisabledPreference(boolean showRipple) {
         mShouldShowRippleOnDisabledPreference = showRipple;
-        updateRippleStateOnDisabledPreference();
+        CarUiUtils.updateRippleStateOnDisabledPreference(isEnabled(),
+                mShouldShowRippleOnDisabledPreference, mBackground, mPreference);
     }
 
-    private void updateRippleStateOnDisabledPreference() {
-        if (isEnabled()) {
-            return;
-        }
-        if (mShouldShowRippleOnDisabledPreference && mPreference != null) {
-            ViewCompat.setBackground(mPreference, mBackground);
-        } else if (mPreference != null) {
-            ViewCompat.setBackground(mPreference, null);
-        }
-    }
-
+    @Override
     public void setMessageToShowWhenDisabledPreferenceClicked(String message) {
         mMessageToShowWhenDisabledPreferenceClicked = message;
     }
diff --git a/car-ui-lib/src/com/android/car/ui/preference/CarUiSwitchPreference.java b/car-ui-lib/src/com/android/car/ui/preference/CarUiSwitchPreference.java
new file mode 100644
index 0000000..25d1c5c
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/preference/CarUiSwitchPreference.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2020 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.car.ui.preference;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Toast;
+
+import androidx.preference.PreferenceViewHolder;
+import androidx.preference.SwitchPreference;
+
+import com.android.car.ui.R;
+import com.android.car.ui.utils.CarUiUtils;
+
+/**
+ * This class extends the base {@link SwitchPreference} class. Adds the functionality to show
+ * message when preference is disabled.
+ */
+public class CarUiSwitchPreference extends SwitchPreference implements IDisabledPreferenceCallback {
+
+    private String mMessageToShowWhenDisabledPreferenceClicked;
+
+    private boolean mShouldShowRippleOnDisabledPreference;
+    private Drawable mBackground;
+    private View mPreference;
+    private Context mContext;
+
+    public CarUiSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr,
+            int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        init(context, attrs);
+    }
+
+    public CarUiSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        init(context, attrs);
+    }
+
+    public CarUiSwitchPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        init(context, attrs);
+    }
+
+    public CarUiSwitchPreference(Context context) {
+        super(context);
+        init(context, null);
+    }
+
+    private void init(Context context, AttributeSet attrs) {
+        mContext = context;
+        TypedArray preferenceAttributes = getContext().obtainStyledAttributes(attrs,
+                R.styleable.CarUiPreference);
+        mShouldShowRippleOnDisabledPreference = preferenceAttributes.getBoolean(
+                R.styleable.CarUiPreference_showRippleOnDisabledPreference, false);
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder holder) {
+        super.onBindViewHolder(holder);
+        mPreference = holder.itemView;
+        mBackground = CarUiUtils.setPreferenceViewEnabled(isEnabled(), holder.itemView, mBackground,
+                mShouldShowRippleOnDisabledPreference);
+    }
+
+    /**
+     * This is similar to {@link Preference#performClick()} with the only difference that we do not
+     * return when view is not enabled.
+     */
+    @Override
+    public void performClick() {
+        if (isEnabled()) {
+            super.performClick();
+        } else if (mMessageToShowWhenDisabledPreferenceClicked != null
+                && !mMessageToShowWhenDisabledPreferenceClicked.isEmpty()) {
+            Toast.makeText(mContext, mMessageToShowWhenDisabledPreferenceClicked,
+                    Toast.LENGTH_LONG).show();
+        }
+    }
+
+    /**
+     * Sets the ripple on the disabled preference.
+     */
+    @Override
+    public void setShouldShowRippleOnDisabledPreference(boolean showRipple) {
+        mShouldShowRippleOnDisabledPreference = showRipple;
+        CarUiUtils.updateRippleStateOnDisabledPreference(isEnabled(),
+                mShouldShowRippleOnDisabledPreference, mBackground, mPreference);
+    }
+
+    @Override
+    public void setMessageToShowWhenDisabledPreferenceClicked(String message) {
+        mMessageToShowWhenDisabledPreferenceClicked = message;
+    }
+}
diff --git a/car-ui-lib/src/com/android/car/ui/preference/IDisabledPreferenceCallback.java b/car-ui-lib/src/com/android/car/ui/preference/IDisabledPreferenceCallback.java
new file mode 100644
index 0000000..39b5bc2
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/preference/IDisabledPreferenceCallback.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2020 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.car.ui.preference;
+
+/**
+ * Interface for preferences to handle clicks when its disabled.
+ */
+public interface IDisabledPreferenceCallback {
+
+    /**
+     * Sets if the ripple effect should be shown on disabled preference.
+     */
+    default void setShouldShowRippleOnDisabledPreference(boolean showRipple) {}
+
+    /**
+     * Sets the message to be displayed when the disabled preference is clicked.
+     */
+    default void setMessageToShowWhenDisabledPreferenceClicked(String message) {}
+}
diff --git a/car-ui-lib/src/com/android/car/ui/preference/PreferenceFragment.java b/car-ui-lib/src/com/android/car/ui/preference/PreferenceFragment.java
index 6bc95f2..260cf6f 100644
--- a/car-ui-lib/src/com/android/car/ui/preference/PreferenceFragment.java
+++ b/car-ui-lib/src/com/android/car/ui/preference/PreferenceFragment.java
@@ -36,6 +36,7 @@
 import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceGroup;
 import androidx.preference.PreferenceScreen;
+import androidx.preference.SwitchPreference;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.android.car.ui.R;
@@ -241,6 +242,7 @@
             new Pair<>(ListPreference.class, CarUiListPreference.class),
             new Pair<>(MultiSelectListPreference.class, CarUiMultiSelectListPreference.class),
             new Pair<>(EditTextPreference.class, CarUiEditTextPreference.class),
+            new Pair<>(SwitchPreference.class, CarUiSwitchPreference.class),
             new Pair<>(Preference.class, CarUiPreference.class)
     );
 
diff --git a/car-ui-lib/src/com/android/car/ui/utils/CarUiUtils.java b/car-ui-lib/src/com/android/car/ui/utils/CarUiUtils.java
index 54d64f1..497d028 100644
--- a/car-ui-lib/src/com/android/car/ui/utils/CarUiUtils.java
+++ b/car-ui-lib/src/com/android/car/ui/utils/CarUiUtils.java
@@ -20,8 +20,10 @@
 import android.content.ContextWrapper;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
 import android.util.TypedValue;
 import android.view.View;
+import android.view.ViewGroup;
 
 import androidx.annotation.DimenRes;
 import androidx.annotation.IdRes;
@@ -29,13 +31,15 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.StyleRes;
 import androidx.annotation.UiThread;
+import androidx.core.view.ViewCompat;
 
 /**
  * Collection of utility methods
  */
 public final class CarUiUtils {
     /** This is a utility class */
-    private CarUiUtils() {}
+    private CarUiUtils() {
+    }
 
     /**
      * Reads a float value from a dimens resource. This is necessary as {@link Resources#getFloat}
@@ -118,4 +122,68 @@
         }
         return view;
     }
+
+    /**
+     * Updates the preference view enabled state. If the view is disabled we just disable the child
+     * of preference like TextView, ImageView. The preference itself is always enabled to get the
+     * click events. Ripple effect in background is also removed by default. If the ripple is
+     * needed see
+     * {@link IDisabledPreferenceCallback#setShouldShowRippleOnDisabledPreference(boolean)}
+     */
+    public static Drawable setPreferenceViewEnabled(boolean viewEnabled, View itemView,
+            Drawable background, boolean shouldShowRippleOnDisabledPreference) {
+        if (viewEnabled) {
+            if (background != null) {
+                ViewCompat.setBackground(itemView, background);
+            }
+            setChildViewsEnabled(itemView, true, false);
+        } else {
+            itemView.setEnabled(true);
+            if (background == null) {
+                // store the original background.
+                background = itemView.getBackground();
+            }
+            updateRippleStateOnDisabledPreference(false, shouldShowRippleOnDisabledPreference,
+                    background, itemView);
+            setChildViewsEnabled(itemView, false, true);
+        }
+        return background;
+    }
+
+    /**
+     * Sets the enabled state on the views of the preference. If the view is being disabled we want
+     * only child views of preference to be disabled.
+     */
+    private static void setChildViewsEnabled(View view, boolean enabled, boolean isRootView) {
+        if (!isRootView) {
+            view.setEnabled(enabled);
+        }
+        if (view instanceof ViewGroup) {
+            ViewGroup grp = (ViewGroup) view;
+            for (int index = 0; index < grp.getChildCount(); index++) {
+                setChildViewsEnabled(grp.getChildAt(index), enabled, false);
+            }
+        }
+    }
+
+    /**
+     * Updates the ripple state on the given preference.
+     *
+     * @param isEnabled whether the preference is enabled or not
+     * @param shouldShowRippleOnDisabledPreference should ripple be displayed when the preference is
+     * clicked
+     * @param background drawable that represents the ripple
+     * @param preference preference on which drawable will be applied
+     */
+    public static void updateRippleStateOnDisabledPreference(boolean isEnabled,
+            boolean shouldShowRippleOnDisabledPreference, Drawable background, View preference) {
+        if (isEnabled || preference == null) {
+            return;
+        }
+        if (shouldShowRippleOnDisabledPreference && background != null) {
+            ViewCompat.setBackground(preference, background);
+        } else {
+            ViewCompat.setBackground(preference, null);
+        }
+    }
 }
diff --git a/car-ui-lib/tests/paintbooth/res/xml/preference_samples.xml b/car-ui-lib/tests/paintbooth/res/xml/preference_samples.xml
index 0a0836a..e483435 100644
--- a/car-ui-lib/tests/paintbooth/res/xml/preference_samples.xml
+++ b/car-ui-lib/tests/paintbooth/res/xml/preference_samples.xml
@@ -87,11 +87,6 @@
             android:summary="@string/summary_switch_preference"
             android:title="@string/title_switch_preference"/>
 
-        <com.android.car.ui.preference.CarUiTwoActionPreference
-            android:key="twoaction"
-            android:summary="@string/summary_twoaction_preference"
-            android:title="@string/title_twoaction_preference"
-            android:widgetLayout="@layout/details_preference_widget"/>
     </PreferenceCategory>
 
     <PreferenceCategory
@@ -119,11 +114,6 @@
             android:summary="@string/summary_multi_list_preference"
             android:title="@string/title_multi_list_preference"/>
 
-        <com.android.car.ui.preference.CarUiSeekBarDialogPreference
-            android:dialogTitle="Seekbar Dialog"
-            android:key="seekbarDialog"
-            android:summary="@string/summary_seekbar_preference"
-            android:title="@string/title_seekbar_preference"/>
     </PreferenceCategory>
 
     <PreferenceCategory
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/preferences/PreferenceDemoFragment.java b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/preferences/PreferenceDemoFragment.java
index e9f990d..233c873 100644
--- a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/preferences/PreferenceDemoFragment.java
+++ b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/preferences/PreferenceDemoFragment.java
@@ -20,6 +20,7 @@
 
 import com.android.car.ui.paintbooth.R;
 import com.android.car.ui.preference.CarUiPreference;
+import com.android.car.ui.preference.CarUiSwitchPreference;
 import com.android.car.ui.preference.PreferenceFragment;
 
 /**
@@ -44,5 +45,10 @@
         preferenceDisabledWithRipple.setMessageToShowWhenDisabledPreferenceClicked(
                 "I am disabled because...");
         preferenceDisabledWithRipple.setShouldShowRippleOnDisabledPreference(true);
+
+        CarUiSwitchPreference carUiSwitchPreference = findPreference("switch");
+        carUiSwitchPreference.setEnabled(false);
+        carUiSwitchPreference.setMessageToShowWhenDisabledPreferenceClicked(
+                "I am disabled because...");
     }
 }