OSDN Git Service

Disables the Gesture nav option if 3P launcher is default
authorMehdi Alizadeh <mett@google.com>
Wed, 15 May 2019 18:01:25 +0000 (11:01 -0700)
committerMehdi Alizadeh <mett@google.com>
Fri, 31 May 2019 19:26:28 +0000 (12:26 -0700)
Disables the Gesture navigation radio button if 3P launcher is set as
default for current user. Also shows an info icon on the right side that
opens a dialog explaining why it is disables.

Bug: 129532605
Test: Manual test with 3P launcher
Test: make RunSettingsRoboTests ROBOTEST_FILTER=RadioButtonPreferenceWithExtraWidgetTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=SystemNavigationGestureSettingsTest
Test: make RunSettingsRoboTests ROBOTEST_FILTER=SystemNavigationPreferenceControllerTest

Change-Id: I90000c74246699fa9391ac042c87d7f0ece03637

res/layout/preference_radio_with_extra_widget.xml [new file with mode: 0644]
src/com/android/settings/gestures/SystemNavigationGestureSettings.java
src/com/android/settings/gestures/SystemNavigationPreferenceController.java
src/com/android/settings/widget/RadioButtonPreferenceWithExtraWidget.java [new file with mode: 0644]
tests/robotests/src/com/android/settings/gestures/SystemNavigationPreferenceControllerTest.java
tests/robotests/src/com/android/settings/widget/RadioButtonPreferenceWithExtraWidgetTest.java [new file with mode: 0644]

diff --git a/res/layout/preference_radio_with_extra_widget.xml b/res/layout/preference_radio_with_extra_widget.xml
new file mode 100644 (file)
index 0000000..00c428f
--- /dev/null
@@ -0,0 +1,106 @@
+<?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
+  -->
+<!-- This file is copied from preference_radio.xml with modification to
+     support an extra clickable icon on the opposite side horizontally -->
+<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="@style/TextAppearance.TileTitle"
+                  android:ellipsize="marquee"
+                  android:fadingEdge="horizontal" />
+
+        <TextView android:id="@android:id/summary"
+                  android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  android:layout_weight="1"
+                  android:textAppearance="@style/TextAppearance.Small"
+                  android:textAlignment="viewStart"
+                  android:textColor="?android:attr/textColorSecondary" />
+    </LinearLayout>
+    <LinearLayout
+        android:id="@+id/radio_extra_widget_container"
+        android:layout_width="wrap_content"
+        android:layout_height="match_parent"
+        android:orientation="horizontal"
+        android:gravity="center_vertical">
+        <View
+            android:id="@+id/radio_extra_widget_divider"
+            android:layout_width="@dimen/vertical_divider_width"
+            android:layout_height="match_parent"
+            android:layout_marginTop="36dp"
+            android:layout_marginBottom="36dp"
+            android:layout_marginStart="8dp"
+            android:background="?android:attr/dividerVertical" />
+        <ImageView
+            android:id="@+id/radio_extra_widget"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:paddingStart="16dp"
+            android:paddingEnd="16dp"
+            android:src="@drawable/ic_settings_about"
+            android:contentDescription="@string/information_label"
+            android:layout_gravity="center"
+            android:background="?android:attr/selectableItemBackground" />
+    </LinearLayout>
+
+</LinearLayout>
index e17b870..40e8831 100644 (file)
@@ -21,6 +21,10 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVE
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
 
+import static com.android.settings.widget.RadioButtonPreferenceWithExtraWidget.EXTRA_WIDGET_VISIBILITY_GONE;
+import static com.android.settings.widget.RadioButtonPreferenceWithExtraWidget.EXTRA_WIDGET_VISIBILITY_INFO;
+
+import android.app.AlertDialog;
 import android.app.settings.SettingsEnums;
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -29,7 +33,6 @@ import android.graphics.drawable.Drawable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.SearchIndexableResource;
-import android.view.View;
 
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.PreferenceScreen;
@@ -41,6 +44,7 @@ import com.android.settings.search.BaseSearchIndexProvider;
 import com.android.settings.search.Indexable;
 import com.android.settings.widget.RadioButtonPickerFragment;
 import com.android.settings.widget.RadioButtonPreference;
+import com.android.settings.widget.RadioButtonPreferenceWithExtraWidget;
 import com.android.settings.widget.VideoPreference;
 import com.android.settingslib.search.SearchIndexable;
 import com.android.settingslib.widget.CandidateInfo;
@@ -94,8 +98,25 @@ public class SystemNavigationGestureSettings extends RadioButtonPickerFragment {
     }
 
     @Override
-    protected void addStaticPreferences(PreferenceScreen screen) {
+    public void updateCandidates() {
+        final String defaultKey = getDefaultKey();
+        final String systemDefaultKey = getSystemDefaultKey();
+        final PreferenceScreen screen = getPreferenceScreen();
+        screen.removeAll();
         screen.addPreference(mVideoPreference);
+
+        final List<? extends CandidateInfo> candidateList = getCandidates();
+        if (candidateList == null) {
+            return;
+        }
+        for (CandidateInfo info : candidateList) {
+            RadioButtonPreferenceWithExtraWidget pref =
+                    new RadioButtonPreferenceWithExtraWidget(getPrefContext());
+            bindPreference(pref, info.getKey(), info, defaultKey);
+            bindPreferenceExtra(pref, info.getKey(), info, defaultKey, systemDefaultKey);
+            screen.addPreference(pref);
+        }
+        mayCheckOnlyRadioButton();
     }
 
     @Override
@@ -135,6 +156,13 @@ public class SystemNavigationGestureSettings extends RadioButtonPickerFragment {
 
     @Override
     protected boolean setDefaultKey(String key) {
+        final Context c = getContext();
+        if (key == KEY_SYSTEM_NAV_GESTURAL &&
+                !SystemNavigationPreferenceController.isGestureNavSupportedByDefaultLauncher(c)) {
+            // This should not happen since the preference is disabled. Return to be safe.
+            return false;
+        }
+
         setCurrentSystemNavigationMode(mOverlayManager, key);
         setIllustrationVideo(mVideoPreference, key);
         return true;
@@ -196,12 +224,38 @@ public class SystemNavigationGestureSettings extends RadioButtonPickerFragment {
     @Override
     public void bindPreferenceExtra(RadioButtonPreference pref,
             String key, CandidateInfo info, String defaultKey, String systemDefaultKey) {
-        if (info instanceof NavModeCandidateInfo) {
-            pref.setSummary(((NavModeCandidateInfo) info).loadSummary());
-            pref.setAppendixVisibility(View.GONE);
+        if (!(info instanceof NavModeCandidateInfo)
+                || !(pref instanceof RadioButtonPreferenceWithExtraWidget)) {
+            return;
+        }
+
+        pref.setSummary(((NavModeCandidateInfo) info).loadSummary());
+
+        RadioButtonPreferenceWithExtraWidget p = (RadioButtonPreferenceWithExtraWidget) pref;
+        if (info.getKey() == KEY_SYSTEM_NAV_GESTURAL
+                && !SystemNavigationPreferenceController.isGestureNavSupportedByDefaultLauncher(
+                        getContext())) {
+            p.setEnabled(false);
+            p.setExtraWidgetVisibility(EXTRA_WIDGET_VISIBILITY_INFO);
+            p.setExtraWidgetOnClickListener((v) -> {
+                showGestureNavDisabledDialog();
+            });
+        } else {
+            p.setExtraWidgetVisibility(EXTRA_WIDGET_VISIBILITY_GONE);
         }
     }
 
+    private void showGestureNavDisabledDialog() {
+        final String defaultHomeAppName = SystemNavigationPreferenceController
+                .getDefaultHomeAppName(getContext());
+        final String message = getString(R.string.gesture_not_supported_dialog_message,
+                defaultHomeAppName);
+        AlertDialog d = new AlertDialog.Builder(getContext())
+                .setMessage(message)
+                .setPositiveButton(R.string.okay, null)
+                .show();
+    }
+
     static class NavModeCandidateInfo extends CandidateInfo {
         private final CharSequence mLabel;
         private final CharSequence mSummary;
index d0d8155..a151dc1 100644 (file)
@@ -22,11 +22,14 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 
 import com.android.settings.R;
 import com.android.settings.core.BasePreferenceController;
 
+import java.util.ArrayList;
+
 public class SystemNavigationPreferenceController extends BasePreferenceController {
 
     static final String PREF_KEY_SYSTEM_NAVIGATION = "gesture_system_navigation";
@@ -98,4 +101,31 @@ public class SystemNavigationPreferenceController extends BasePreferenceControll
         return NAV_BAR_MODE_GESTURAL == context.getResources().getInteger(
                 com.android.internal.R.integer.config_navBarInteractionMode);
     }
+
+    static boolean isGestureNavSupportedByDefaultLauncher(Context context) {
+        final ComponentName cn = context.getPackageManager().getHomeActivities(new ArrayList<>());
+        if (cn == null) {
+            // There is no default home app set for the current user, don't make any changes yet.
+            return true;
+        }
+        ComponentName recentsComponentName = ComponentName.unflattenFromString(context.getString(
+                com.android.internal.R.string.config_recentsComponentName));
+        return recentsComponentName.getPackageName().equals(cn.getPackageName());
+    }
+
+    static String getDefaultHomeAppName(Context context) {
+        final PackageManager pm = context.getPackageManager();
+        final ComponentName cn = pm.getHomeActivities(new ArrayList<>());
+        if (cn != null) {
+            try {
+                ApplicationInfo ai = pm.getApplicationInfo(cn.getPackageName(), 0);
+                if (ai != null) {
+                    return pm.getApplicationLabel(ai).toString();
+                }
+            } catch (final PackageManager.NameNotFoundException e) {
+                // Do nothing
+            }
+        }
+        return "";
+    }
 }
diff --git a/src/com/android/settings/widget/RadioButtonPreferenceWithExtraWidget.java b/src/com/android/settings/widget/RadioButtonPreferenceWithExtraWidget.java
new file mode 100644 (file)
index 0000000..6a47ce5
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * 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.settings.widget;
+
+import android.content.Context;
+import android.view.View;
+import android.widget.ImageView;
+
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+
+public class RadioButtonPreferenceWithExtraWidget extends RadioButtonPreference {
+    public static final int EXTRA_WIDGET_VISIBILITY_GONE = 0;
+    public static final int EXTRA_WIDGET_VISIBILITY_INFO = 1;
+
+    private View mExtraWidgetDivider;
+    private ImageView mExtraWidget;
+
+    private int mExtraWidgetVisibility = EXTRA_WIDGET_VISIBILITY_GONE;
+    private View.OnClickListener mExtraWidgetOnClickListener;
+
+    public RadioButtonPreferenceWithExtraWidget(Context context) {
+        super(context, null);
+        setLayoutResource(R.layout.preference_radio_with_extra_widget);
+    }
+
+    @Override
+    public void onBindViewHolder(PreferenceViewHolder view) {
+        super.onBindViewHolder(view);
+
+        mExtraWidget = (ImageView) view.findViewById(R.id.radio_extra_widget);
+        mExtraWidgetDivider = view.findViewById(R.id.radio_extra_widget_divider);
+        setExtraWidgetVisibility(mExtraWidgetVisibility);
+
+        if (mExtraWidgetOnClickListener != null) {
+            setExtraWidgetOnClickListener(mExtraWidgetOnClickListener);
+        }
+    }
+
+    public void setExtraWidgetVisibility(int visibility) {
+        mExtraWidgetVisibility = visibility;
+        if (mExtraWidget == null || mExtraWidgetDivider == null) {
+            return;
+        }
+
+        if (visibility == EXTRA_WIDGET_VISIBILITY_GONE) {
+            mExtraWidget.setClickable(false);
+            mExtraWidget.setVisibility(View.GONE);
+            mExtraWidgetDivider.setVisibility(View.GONE);
+        } else {
+            mExtraWidget.setClickable(true);
+            mExtraWidget.setVisibility(View.VISIBLE);
+            mExtraWidgetDivider.setVisibility(View.VISIBLE);
+        }
+    }
+
+    public void setExtraWidgetOnClickListener(View.OnClickListener listener) {
+        mExtraWidgetOnClickListener = listener;
+        if (mExtraWidget != null) {
+            mExtraWidget.setEnabled(true);
+            mExtraWidget.setOnClickListener(listener);
+        }
+    }
+}
\ No newline at end of file
index 095c4d7..79f1850 100644 (file)
@@ -24,10 +24,14 @@ import static com.android.settings.gestures.SystemNavigationPreferenceController
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.text.TextUtils;
@@ -39,6 +43,7 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.RuntimeEnvironment;
@@ -53,9 +58,15 @@ public class SystemNavigationPreferenceControllerTest {
     private Context mContext;
     private ShadowPackageManager mPackageManager;
 
+    @Mock
+    private Context mMockContext;
+    @Mock
+    private PackageManager mMockPackageManager;
+
     private SystemNavigationPreferenceController mController;
 
     private static final String ACTION_QUICKSTEP = "android.intent.action.QUICKSTEP_SERVICE";
+    private static final String TEST_RECENTS_COMPONENT_NAME = "test.component.name/.testActivity";
 
     @Before
     public void setUp() {
@@ -69,6 +80,10 @@ public class SystemNavigationPreferenceControllerTest {
 
         mController = new SystemNavigationPreferenceController(mContext,
                 PREF_KEY_SYSTEM_NAVIGATION);
+
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockContext.getString(com.android.internal.R.string.config_recentsComponentName))
+                .thenReturn(TEST_RECENTS_COMPONENT_NAME);
     }
 
     @After
@@ -166,4 +181,46 @@ public class SystemNavigationPreferenceControllerTest {
         assertThat(TextUtils.equals(mController.getSummary(), mContext.getText(
                 com.android.settings.R.string.swipe_up_to_switch_apps_title))).isTrue();
     }
+
+    @Test
+    public void testIsGestureNavSupportedByDefaultLauncher_noDefaultLauncher() {
+        when(mMockPackageManager.getHomeActivities(any())).thenReturn(null);
+        assertThat(SystemNavigationPreferenceController
+                .isGestureNavSupportedByDefaultLauncher(mMockContext)).isTrue();
+    }
+
+    @Test
+    public void testIsGestureNavSupportedByDefaultLauncher_supported() {
+        when(mMockPackageManager.getHomeActivities(any())).thenReturn(
+                ComponentName.unflattenFromString(TEST_RECENTS_COMPONENT_NAME));
+        assertThat(SystemNavigationPreferenceController
+                .isGestureNavSupportedByDefaultLauncher(mMockContext)).isTrue();
+    }
+
+    @Test
+    public void testIsGestureNavSupportedByDefaultLauncher_notSupported() {
+        when(mMockPackageManager.getHomeActivities(any())).thenReturn(
+                new ComponentName("unsupported", "launcher"));
+        assertThat(SystemNavigationPreferenceController
+                .isGestureNavSupportedByDefaultLauncher(mMockContext)).isFalse();
+    }
+
+    @Test
+    public void testGetDefaultHomeAppName_noDefaultLauncher() {
+        when(mMockPackageManager.getHomeActivities(any())).thenReturn(null);
+        assertThat(SystemNavigationPreferenceController
+                .getDefaultHomeAppName(mMockContext)).isEqualTo("");
+    }
+
+    @Test
+    public void testGetDefaultHomeAppName_defaultLauncherExists() throws Exception {
+        when(mMockPackageManager.getHomeActivities(any())).thenReturn(
+                new ComponentName("supported", "launcher"));
+        ApplicationInfo info = new ApplicationInfo();
+        when(mMockPackageManager.getApplicationInfo("supported", 0)).thenReturn(info);
+        when(mMockPackageManager.getApplicationLabel(info)).thenReturn("Test Home App");
+
+        assertThat(SystemNavigationPreferenceController
+                .getDefaultHomeAppName(mMockContext)).isEqualTo("Test Home App");
+    }
 }
\ No newline at end of file
diff --git a/tests/robotests/src/com/android/settings/widget/RadioButtonPreferenceWithExtraWidgetTest.java b/tests/robotests/src/com/android/settings/widget/RadioButtonPreferenceWithExtraWidgetTest.java
new file mode 100644 (file)
index 0000000..b84b3bb
--- /dev/null
@@ -0,0 +1,127 @@
+/*
+ * 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.settings.widget;
+
+import static com.android.settings.widget.RadioButtonPreferenceWithExtraWidget.EXTRA_WIDGET_VISIBILITY_GONE;
+import static com.android.settings.widget.RadioButtonPreferenceWithExtraWidget.EXTRA_WIDGET_VISIBILITY_INFO;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.Application;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settings.R;
+
+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 RadioButtonPreferenceWithExtraWidgetTest {
+
+    private Application mContext;
+    private RadioButtonPreferenceWithExtraWidget mPreference;
+
+    private TextView mSummary;
+    private ImageView mExtraWidget;
+    private View mExtraWidgetDivider;
+
+    private boolean mIsClickListenerCalled = false;
+    private View.OnClickListener mClickListener = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            mIsClickListenerCalled = true;
+        }
+    };
+
+    @Before
+    public void setUp() {
+        mContext = RuntimeEnvironment.application;
+        mPreference = new RadioButtonPreferenceWithExtraWidget(mContext);
+        mPreference.setSummary("test summary");
+
+        View view = LayoutInflater.from(mContext)
+                .inflate(R.layout.preference_radio_with_extra_widget, null);
+        PreferenceViewHolder preferenceViewHolder =
+                PreferenceViewHolder.createInstanceForTests(view);
+        mPreference.onBindViewHolder(preferenceViewHolder);
+
+        mSummary = view.findViewById(android.R.id.summary);
+        mExtraWidget = view.findViewById(R.id.radio_extra_widget);
+        mExtraWidgetDivider = view.findViewById(R.id.radio_extra_widget_divider);
+    }
+
+    @Test
+    public void shouldHaveRadioPreferenceWithExtraWidgetLayout() {
+        assertThat(mPreference.getLayoutResource())
+                .isEqualTo(R.layout.preference_radio_with_extra_widget);
+    }
+
+    @Test
+    public void iconSpaceReservedShouldBeFalse() {
+        assertThat(mPreference.isIconSpaceReserved()).isFalse();
+    }
+
+    @Test
+    public void summaryShouldBeVisible() {
+        assertEquals(View.VISIBLE, mSummary.getVisibility());
+    }
+
+    @Test
+    public void testSetExtraWidgetVisibility_gone() {
+        mPreference.setExtraWidgetVisibility(EXTRA_WIDGET_VISIBILITY_GONE);
+        assertEquals(View.GONE, mExtraWidget.getVisibility());
+        assertEquals(View.GONE, mExtraWidgetDivider.getVisibility());
+        assertThat(mExtraWidget.isClickable()).isFalse();
+    }
+
+    @Test
+    public void testSetExtraWidgetVisibility_info() {
+        mPreference.setExtraWidgetVisibility(EXTRA_WIDGET_VISIBILITY_INFO);
+        assertEquals(View.VISIBLE, mExtraWidget.getVisibility());
+        assertEquals(View.VISIBLE, mExtraWidgetDivider.getVisibility());
+        assertThat(mExtraWidget.isClickable()).isTrue();
+    }
+
+    @Test
+    public void testSetExtraWidgetOnClickListener() {
+        mPreference.setExtraWidgetOnClickListener(mClickListener);
+
+        assertThat(mIsClickListenerCalled).isFalse();
+        mExtraWidget.callOnClick();
+        assertThat(mIsClickListenerCalled).isTrue();
+    }
+
+    @Test
+    public void extraWidgetStaysEnabledWhenPreferenceIsDisabled() {
+        mPreference.setEnabled(false);
+        mExtraWidget.setEnabled(false);
+
+        assertThat(mExtraWidget.isEnabled()).isFalse();
+        mPreference.setExtraWidgetOnClickListener(mClickListener);
+        assertThat(mExtraWidget.isEnabled()).isTrue();
+    }
+}