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

Commit 9fbc52f0 authored by Andy Wickham's avatar Andy Wickham
Browse files

Add CUSTOM_LPNH_THRESHOLDS feature flag to customize LPNH

When this flag is enabled, new Developer Options appear which
allow you to customize the slop and timeout to invoke LPNH.

The slop is defined as a multiplier to the default edge slop
(which I discovered while looking into this - it appears to
be intended for touches along the device edge and is 50%
larger than the default touch slop; currently used by Back).

Timeout is defined in milliseconds, defaulting to 400, at least
on my device.

Bug: 301680992
Bug: 300955321
Flag: CUSTOM_LPNH_THRESHOLDS - should be no-op with default off
Test: Manual with flag on and adjusting sliders, and flag off
Change-Id: Iabc7b3706f4fdd6f0392c858b81a856e375ffd51
parent 186e60e9
Loading
Loading
Loading
Loading
+45 −16
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ import static android.view.View.GONE;
import static android.view.View.VISIBLE;

import static com.android.launcher3.LauncherPrefs.ALL_APPS_OVERVIEW_THRESHOLD;
import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE;
import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_TIMEOUT_MS;
import static com.android.launcher3.settings.SettingsActivity.EXTRA_FRAGMENT_ARG_KEY;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.PLUGIN_CHANGED;
import static com.android.launcher3.uioverrides.plugins.PluginManagerWrapper.pluginEnabledKey;
@@ -63,6 +65,7 @@ import androidx.preference.PreferenceViewHolder;
import androidx.preference.SeekBarPreference;
import androidx.preference.SwitchPreference;

import com.android.launcher3.ConstantItem;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
@@ -111,6 +114,9 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {
        if (FeatureFlags.ENABLE_ALL_APPS_FROM_OVERVIEW.get()) {
            addAllAppsFromOverviewCatergory();
        }
        if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
            addCustomLpnhCatergory();
        }

        if (getActivity() != null) {
            getActivity().setTitle("Developer Options");
@@ -400,29 +406,52 @@ public class DeveloperOptionsFragment extends PreferenceFragmentCompat {

    private void addAllAppsFromOverviewCatergory() {
        PreferenceCategory category = newCategory("All Apps from Overview Config");
        category.addPreference(createSeekBarPreference("Threshold to open All Apps from Overview",
                105, 500, 100, ALL_APPS_OVERVIEW_THRESHOLD));
    }

        SeekBarPreference thresholdPref = new SeekBarPreference(getContext());
        thresholdPref.setTitle("Threshold to open All Apps from Overview");
        thresholdPref.setSingleLineTitle(false);
    private void addCustomLpnhCatergory() {
        PreferenceCategory category = newCategory("Long Press Nav Handle Config");
        category.addPreference(createSeekBarPreference("Slop multiplier (applied to edge slop, "
                        + "which is generally already 50% higher than touch slop)",
                25, 200, 100, LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE));
        category.addPreference(createSeekBarPreference("Trigger milliseconds",
                100, 500, 1, LONG_PRESS_NAV_HANDLE_TIMEOUT_MS));
    }

        // These values are 100x swipe up shift value (100 = where overview sits).
        thresholdPref.setMax(500);
        thresholdPref.setMin(105);
        thresholdPref.setUpdatesContinuously(true);
        thresholdPref.setIconSpaceReserved(false);
    /**
     * Create a preference with text and a seek bar. Should be added to a PreferenceCategory.
     *
     * @param title text to show for this seek bar
     * @param min min value for the seek bar
     * @param max max value for the seek bar
     * @param scale how much to divide the value to convert int to float
     * @param launcherPref used to store the current value
     */
    private SeekBarPreference createSeekBarPreference(String title, int min, int max, int scale,
            ConstantItem<Integer> launcherPref) {
        SeekBarPreference seekBarPref = new SeekBarPreference(getContext());
        seekBarPref.setTitle(title);
        seekBarPref.setSingleLineTitle(false);

        seekBarPref.setMax(max);
        seekBarPref.setMin(min);
        seekBarPref.setUpdatesContinuously(true);
        seekBarPref.setIconSpaceReserved(false);
        // Don't directly save to shared prefs, use LauncherPrefs instead.
        thresholdPref.setPersistent(false);
        thresholdPref.setOnPreferenceChangeListener((preference, newValue) -> {
            LauncherPrefs.get(getContext()).put(ALL_APPS_OVERVIEW_THRESHOLD, newValue);
            preference.setSummary(String.valueOf((int) newValue / 100f));
        seekBarPref.setPersistent(false);
        seekBarPref.setOnPreferenceChangeListener((preference, newValue) -> {
            LauncherPrefs.get(getContext()).put(launcherPref, newValue);
            preference.setSummary(String.valueOf(scale == 1 ? newValue
                    : (int) newValue / (float) scale));
            return true;
        });
        int value = LauncherPrefs.get(getContext()).get(ALL_APPS_OVERVIEW_THRESHOLD);
        thresholdPref.setValue(value);
        int value = LauncherPrefs.get(getContext()).get(launcherPref);
        seekBarPref.setValue(value);
        // For some reason the initial value is not triggering the summary update, so call manually.
        thresholdPref.getOnPreferenceChangeListener().onPreferenceChange(thresholdPref, value);
        seekBarPref.getOnPreferenceChangeListener().onPreferenceChange(seekBarPref, value);

        category.addPreference(thresholdPref);
        return seekBarPref;
    }

    private String toName(String action) {
+58 −3
Original line number Diff line number Diff line
@@ -15,14 +15,19 @@
 */
package com.android.quickstep.inputconsumers;

import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE;
import static com.android.launcher3.LauncherPrefs.LONG_PRESS_NAV_HANDLE_TIMEOUT_MS;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;

import android.content.Context;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.ViewConfiguration;

import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.InputConsumer;
import com.android.systemui.shared.system.InputMonitorCompat;
@@ -37,19 +42,31 @@ public class NavHandleLongPressInputConsumer extends DelegateInputConsumer {
    private final float mNavHandleWidth;
    private final float mScreenWidth;

    // Below are only used if CUSTOM_LPNH_THRESHOLDS is enabled.
    private final float mCustomTouchSlopSquared;
    private final int mCustomLongPressTimeout;
    private final Runnable mTriggerCustomLongPress = this::triggerCustomLongPress;
    private MotionEvent mCurrentCustomDownEvent;

    public NavHandleLongPressInputConsumer(Context context, InputConsumer delegate,
            InputMonitorCompat inputMonitor) {
        super(delegate, inputMonitor);
        mNavHandleWidth = context.getResources().getDimensionPixelSize(
                R.dimen.navigation_home_handle_width);
        mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
        float customSlopMultiplier =
                LauncherPrefs.get(context).get(LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE) / 100f;
        float customTouchSlop =
                ViewConfiguration.get(context).getScaledEdgeSlop() * customSlopMultiplier;
        mCustomTouchSlopSquared = customTouchSlop * customTouchSlop;
        mCustomLongPressTimeout = LauncherPrefs.get(context).get(LONG_PRESS_NAV_HANDLE_TIMEOUT_MS);

        mNavHandleLongPressHandler = NavHandleLongPressHandler.newInstance(context);

        mLongPressDetector = new GestureDetector(context, new SimpleOnGestureListener() {
            @Override
            public void onLongPress(MotionEvent motionEvent) {
                if (isInArea(motionEvent.getRawX())) {
                if (isInNavBarHorizontalArea(motionEvent.getRawX())) {
                    Runnable longPressRunnable = mNavHandleLongPressHandler.getLongPressRunnable();
                    if (longPressRunnable != null) {
                        setActive(motionEvent);
@@ -68,13 +85,51 @@ public class NavHandleLongPressInputConsumer extends DelegateInputConsumer {

    @Override
    public void onMotionEvent(MotionEvent ev) {
        if (!FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
            mLongPressDetector.onTouchEvent(ev);
        } else {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN -> {
                    if (mCurrentCustomDownEvent != null) {
                        mCurrentCustomDownEvent.recycle();
                    }
                    mCurrentCustomDownEvent = MotionEvent.obtain(ev);
                    if (isInNavBarHorizontalArea(ev.getRawX())) {
                        MAIN_EXECUTOR.getHandler().postDelayed(mTriggerCustomLongPress,
                                mCustomLongPressTimeout);
                    }
                }
                case MotionEvent.ACTION_MOVE -> {
                    double touchDeltaSquared =
                            Math.pow(ev.getX() - mCurrentCustomDownEvent.getX(), 2)
                            + Math.pow(ev.getY() - mCurrentCustomDownEvent.getY(), 2);
                    if (touchDeltaSquared > mCustomTouchSlopSquared) {
                        cancelCustomLongPress();
                    }
                }
                case MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> cancelCustomLongPress();
            }
        }

        if (mState != STATE_ACTIVE) {
            mDelegate.onMotionEvent(ev);
        }
    }

    protected boolean isInArea(float x) {
    private void triggerCustomLongPress() {
        Runnable longPressRunnable = mNavHandleLongPressHandler.getLongPressRunnable();
        if (longPressRunnable != null) {
            setActive(mCurrentCustomDownEvent);

            MAIN_EXECUTOR.post(longPressRunnable);
        }
    }

    private void cancelCustomLongPress() {
        MAIN_EXECUTOR.getHandler().removeCallbacks(mTriggerCustomLongPress);
    }

    private boolean isInNavBarHorizontalArea(float x) {
        float areaFromMiddle = mNavHandleWidth / 2.0f;
        float distFromMiddle = Math.abs(mScreenWidth / 2.0f - x);

+4 −0
Original line number Diff line number Diff line
@@ -66,6 +66,10 @@ public class LauncherAppState implements SafeCloseable {
    public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher";
    public static final String KEY_ICON_STATE = "pref_icon_shape_path";
    public static final String KEY_ALL_APPS_OVERVIEW_THRESHOLD = "pref_all_apps_overview_threshold";
    public static final String KEY_LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE =
            "pref_long_press_nav_handle_slop_multiplier";
    public static final String KEY_LONG_PRESS_NAV_HANDLE_TIMEOUT_MS =
            "pref_long_press_nav_handle_timeout_ms";

    // We do not need any synchronization for this variable as its only written on UI thread.
    public static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
+15 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.util.Log
import android.view.ViewConfiguration
import androidx.annotation.VisibleForTesting
import com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN
import com.android.launcher3.LauncherFiles.DEVICE_PREFERENCES_KEY
@@ -309,6 +310,20 @@ class LauncherPrefs(private val encryptedContext: Context) {
                EncryptionType.MOVE_TO_DEVICE_PROTECTED
            )
        @JvmField
        val LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE =
            nonRestorableItem(
                LauncherAppState.KEY_LONG_PRESS_NAV_HANDLE_SLOP_PERCENTAGE,
                100,
                EncryptionType.MOVE_TO_DEVICE_PROTECTED
            )
        @JvmField
        val LONG_PRESS_NAV_HANDLE_TIMEOUT_MS =
            nonRestorableItem(
                LauncherAppState.KEY_LONG_PRESS_NAV_HANDLE_TIMEOUT_MS,
                ViewConfiguration.getLongPressTimeout(),
                EncryptionType.MOVE_TO_DEVICE_PROTECTED
            )
        @JvmField
        val THEMED_ICONS =
            backedUpItem(Themes.KEY_THEMED_ICONS, false, EncryptionType.MOVE_TO_DEVICE_PROTECTED)
        @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
+4 −0
Original line number Diff line number Diff line
@@ -119,6 +119,10 @@ public final class FeatureFlags {
            getDebugFlag(275132633, "ENABLE_ALL_APPS_FROM_OVERVIEW", DISABLED,
                    "Allow entering All Apps from Overview (e.g. long swipe up from app)");

    public static final BooleanFlag CUSTOM_LPNH_THRESHOLDS =
            getDebugFlag(301680992, "CUSTOM_LPNH_THRESHOLDS", DISABLED,
                    "Add dev options to customize the LPNH trigger slop and milliseconds");

    public static final BooleanFlag ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS = getReleaseFlag(
            270394468, "ENABLE_SHOW_KEYBOARD_OPTION_IN_ALL_APPS", ENABLED,
            "Enable option to show keyboard when going to all-apps");