From ffea2ae4889efb93373ea81955e05400428f20a2 Mon Sep 17 00:00:00 2001 From: Mehdi Alizadeh Date: Wed, 15 May 2019 11:01:25 -0700 Subject: [PATCH] Disables the Gesture nav option if 3P launcher is default 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 | 106 +++++++++++++++++ .../gestures/SystemNavigationGestureSettings.java | 64 ++++++++++- .../SystemNavigationPreferenceController.java | 30 +++++ .../RadioButtonPreferenceWithExtraWidget.java | 79 +++++++++++++ .../SystemNavigationPreferenceControllerTest.java | 57 +++++++++ .../RadioButtonPreferenceWithExtraWidgetTest.java | 127 +++++++++++++++++++++ 6 files changed, 458 insertions(+), 5 deletions(-) create mode 100644 res/layout/preference_radio_with_extra_widget.xml create mode 100644 src/com/android/settings/widget/RadioButtonPreferenceWithExtraWidget.java create mode 100644 tests/robotests/src/com/android/settings/widget/RadioButtonPreferenceWithExtraWidgetTest.java diff --git a/res/layout/preference_radio_with_extra_widget.xml b/res/layout/preference_radio_with_extra_widget.xml new file mode 100644 index 0000000000..00c428f273 --- /dev/null +++ b/res/layout/preference_radio_with_extra_widget.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/com/android/settings/gestures/SystemNavigationGestureSettings.java b/src/com/android/settings/gestures/SystemNavigationGestureSettings.java index e17b870570..40e8831e5c 100644 --- a/src/com/android/settings/gestures/SystemNavigationGestureSettings.java +++ b/src/com/android/settings/gestures/SystemNavigationGestureSettings.java @@ -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 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; diff --git a/src/com/android/settings/gestures/SystemNavigationPreferenceController.java b/src/com/android/settings/gestures/SystemNavigationPreferenceController.java index d0d81552ff..a151dc1746 100644 --- a/src/com/android/settings/gestures/SystemNavigationPreferenceController.java +++ b/src/com/android/settings/gestures/SystemNavigationPreferenceController.java @@ -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 index 0000000000..6a47ce516a --- /dev/null +++ b/src/com/android/settings/widget/RadioButtonPreferenceWithExtraWidget.java @@ -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 diff --git a/tests/robotests/src/com/android/settings/gestures/SystemNavigationPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/gestures/SystemNavigationPreferenceControllerTest.java index 095c4d78f1..79f18502c8 100644 --- a/tests/robotests/src/com/android/settings/gestures/SystemNavigationPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/gestures/SystemNavigationPreferenceControllerTest.java @@ -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 index 0000000000..b84b3bb48d --- /dev/null +++ b/tests/robotests/src/com/android/settings/widget/RadioButtonPreferenceWithExtraWidgetTest.java @@ -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(); + } +} -- 2.11.0