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
This commit is contained in:
Mehdi Alizadeh
2019-05-15 11:01:25 -07:00
parent be98ea25ca
commit ffea2ae488
6 changed files with 458 additions and 5 deletions

View File

@@ -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>

View 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,10 +224,36 @@ 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 {

View 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 "";
}
}

View File

@@ -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);
}
}
}

View 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");
}
}

View File

@@ -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();
}
}