Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ffea2ae4 authored by Mehdi Alizadeh's avatar Mehdi Alizadeh
Browse files

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
parent be98ea25
Loading
Loading
Loading
Loading
+106 −0
Original line number Diff line number Diff line
<?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>
+59 −5
Original line number Diff line number Diff line
@@ -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) {
        if (!(info instanceof NavModeCandidateInfo)
                || !(pref instanceof RadioButtonPreferenceWithExtraWidget)) {
            return;
        }

        pref.setSummary(((NavModeCandidateInfo) info).loadSummary());
            pref.setAppendixVisibility(View.GONE);

        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 {
+30 −0
Original line number Diff line number Diff line
@@ -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 "";
    }
}
+79 −0
Original line number Diff line number Diff line
/*
 * 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
+57 −0
Original line number Diff line number Diff line
@@ -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
Loading