Merge "Add CUSTOM_LPNH_THRESHOLDS feature flag to customize LPNH" into main
diff --git a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java
index a76eb43..cb65c53 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/flags/DeveloperOptionsFragment.java
@@ -24,6 +24,8 @@
 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.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 @@
         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 @@
 
     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) {
diff --git a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
index fc3f3ab..f9f1579 100644
--- a/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
+++ b/quickstep/src/com/android/quickstep/inputconsumers/NavHandleLongPressInputConsumer.java
@@ -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 @@
     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) {
                         OtherActivityInputConsumer oaic = getInputConsumerOfClass(
@@ -74,13 +91,51 @@
 
     @Override
     public void onMotionEvent(MotionEvent ev) {
-        mLongPressDetector.onTouchEvent(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);
 
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index ab41a31..8d19040 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -66,6 +66,10 @@
     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 =
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index f2df230..a26e622 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -20,6 +20,7 @@
 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
@@ -308,6 +309,20 @@
                 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, "")
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index c70e786..13aa687 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -119,6 +119,10 @@
             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");