Merge changes from topic "ub-one-handed-rebase" into ub-launcher3-master

* changes:
  Guard swipe to notification gesture with system settings preference
  Update state and touch region after one handed overlay changed
  Hook one-handed gesture to expand notification panel by default
  Enable one-handed mode gestural in QuickStep
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 8837c0e..e5852be 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -58,6 +58,7 @@
 
 import com.android.launcher3.BaseDraggingActivity;
 import com.android.launcher3.R;
+import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.Utilities;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.logging.UserEventDispatcher;
@@ -74,6 +75,7 @@
 import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
 import com.android.quickstep.inputconsumers.AssistantInputConsumer;
 import com.android.quickstep.inputconsumers.DeviceLockedInputConsumer;
+import com.android.quickstep.inputconsumers.OneHandedModeInputConsumer;
 import com.android.quickstep.inputconsumers.OtherActivityInputConsumer;
 import com.android.quickstep.inputconsumers.OverscrollInputConsumer;
 import com.android.quickstep.inputconsumers.OverviewInputConsumer;
@@ -297,6 +299,7 @@
         mAM = ActivityManagerWrapper.getInstance();
         mDeviceState = new RecentsAnimationDeviceState(this);
         mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
+        mDeviceState.addOneHandedModeChangedCallback(this::onOneHandedModeOverlayChanged);
         mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
         mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
         ProtoTracer.INSTANCE.get(this).add(this);
@@ -338,6 +341,13 @@
         resetHomeBounceSeenOnQuickstepEnabledFirstTime();
     }
 
+    /**
+     * Called when the one handed mode overlay package changes, to recreate touch region.
+     */
+    private void onOneHandedModeOverlayChanged(int newGesturalHeight) {
+        initInputMonitor();
+    }
+
     @UiThread
     public void onUserUnlocked() {
         mTaskAnimationManager = new TaskAnimationManager();
@@ -500,6 +510,11 @@
                             mGestureState,
                             InputConsumer.NO_OP, mInputMonitorCompat,
                             mOverviewComponentObserver.assistantGestureIsConstrained());
+                } else if (mDeviceState.canTriggerOneHandedAction(event)
+                    && !mDeviceState.isOneHandedModeActive()) {
+                    // Consume gesture event for triggering one handed feature.
+                    mUncheckedConsumer = new OneHandedModeInputConsumer(this, mDeviceState,
+                        InputConsumer.NO_OP, mInputMonitorCompat);
                 } else {
                     mUncheckedConsumer = InputConsumer.NO_OP;
                 }
@@ -627,6 +642,11 @@
                 base = new ScreenPinnedInputConsumer(this, newGestureState);
             }
 
+            if (mDeviceState.canTriggerOneHandedAction(event)) {
+                base = new OneHandedModeInputConsumer(this, mDeviceState, base,
+                        mInputMonitorCompat);
+            }
+
             if (mDeviceState.isAccessibilityMenuAvailable()) {
                 base = new AccessibilityInputConsumer(this, mDeviceState, base,
                         mInputMonitorCompat);
@@ -635,6 +655,11 @@
             if (mDeviceState.isScreenPinningActive()) {
                 base = mResetGestureInputConsumer;
             }
+
+            if (mDeviceState.canTriggerOneHandedAction(event)) {
+                base = new OneHandedModeInputConsumer(this, mDeviceState, base,
+                        mInputMonitorCompat);
+            }
         }
         return base;
     }
@@ -796,6 +821,13 @@
         }
         if (mOverviewComponentObserver.canHandleConfigChanges(activity.getComponentName(),
                 activity.getResources().getConfiguration().diff(newConfig))) {
+            // Since navBar gestural height are different between portrait and landscape,
+            // can handle orientation changes and refresh navigation gestural region through
+            // onOneHandedModeChanged()
+            int newGesturalHeight = ResourceUtils.getNavbarSize(
+                    ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
+                    getApplicationContext().getResources());
+            mDeviceState.onOneHandedModeChanged(newGesturalHeight);
             return;
         }
 
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
new file mode 100644
index 0000000..0bb8fff
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/inputconsumers/OneHandedModeInputConsumer.java
@@ -0,0 +1,159 @@
+/*
+ * 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.quickstep.inputconsumers;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static com.android.launcher3.Utilities.squaredHypot;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.quickstep.InputConsumer;
+import com.android.quickstep.RecentsAnimationDeviceState;
+import com.android.quickstep.SystemUiProxy;
+import com.android.systemui.shared.system.InputMonitorCompat;
+
+/**
+ * Touch consumer for handling gesture event to launch one handed
+ * One handed gestural in quickstep only active on NO_BUTTON, TWO_BUTTONS, and portrait mode
+ */
+public class OneHandedModeInputConsumer extends DelegateInputConsumer {
+
+    private static final String TAG = "OneHandedModeInputConsumer";
+    private static final int ANGLE_MAX = 150;
+    private static final int ANGLE_MIN = 30;
+
+    private final Context mContext;
+    private final RecentsAnimationDeviceState mDeviceState;
+
+    private final float mDragDistThreshold;
+    private final float mSquaredSlop;
+
+    private final PointF mDownPos = new PointF();
+    private final PointF mLastPos = new PointF();
+    private final PointF mStartDragPos = new PointF();
+
+    private boolean mPassedSlop;
+
+    public OneHandedModeInputConsumer(Context context, RecentsAnimationDeviceState deviceState,
+            InputConsumer delegate, InputMonitorCompat inputMonitor) {
+        super(delegate, inputMonitor);
+        mContext = context;
+        mDeviceState = deviceState;
+        mDragDistThreshold = context.getResources().getDimensionPixelSize(
+                R.dimen.gestures_onehanded_drag_threshold);
+        mSquaredSlop = Utilities.squaredTouchSlop(context);
+    }
+
+    @Override
+    public int getType() {
+        return TYPE_ONE_HANDED | mDelegate.getType();
+    }
+
+    @Override
+    public void onMotionEvent(MotionEvent ev) {
+        switch (ev.getActionMasked()) {
+            case ACTION_DOWN: {
+                mDownPos.set(ev.getX(), ev.getY());
+                mLastPos.set(mDownPos);
+                break;
+            }
+            case ACTION_MOVE: {
+                if (mState == STATE_DELEGATE_ACTIVE) {
+                    break;
+                }
+                if (!mDelegate.allowInterceptByParent()) {
+                    mState = STATE_DELEGATE_ACTIVE;
+                    break;
+                }
+
+                mLastPos.set(ev.getX(), ev.getY());
+                if (!mPassedSlop) {
+                    if (squaredHypot(mLastPos.x - mDownPos.x, mLastPos.y - mDownPos.y)
+                            > mSquaredSlop) {
+                        mStartDragPos.set(mLastPos.x, mLastPos.y);
+                        if ((!mDeviceState.isOneHandedModeActive() && isValidStartAngle(
+                                mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y))
+                                || (mDeviceState.isOneHandedModeActive() && isValidExitAngle(
+                                mDownPos.x - mLastPos.x, mDownPos.y - mLastPos.y))) {
+                            mPassedSlop = true;
+                            setActive(ev);
+                        } else {
+                            mState = STATE_DELEGATE_ACTIVE;
+                        }
+                    }
+                } else {
+                    float distance = (float) Math.hypot(mLastPos.x - mStartDragPos.x,
+                            mLastPos.y - mStartDragPos.y);
+                    if (distance > mDragDistThreshold && mPassedSlop) {
+                        onStopGestureDetected();
+                    }
+                }
+                break;
+            }
+            case ACTION_UP:
+            case ACTION_CANCEL: {
+                if (mLastPos.y >= mStartDragPos.y && mPassedSlop) {
+                    onStartGestureDetected();
+                }
+
+                mPassedSlop = false;
+                mState = STATE_INACTIVE;
+                break;
+            }
+        }
+
+        if (mState != STATE_ACTIVE) {
+            mDelegate.onMotionEvent(ev);
+        }
+    }
+
+    private void onStartGestureDetected() {
+        if (mDeviceState.isOneHandedModeEnabled()) {
+            if (!mDeviceState.isOneHandedModeActive()) {
+                SystemUiProxy.INSTANCE.get(mContext).startOneHandedMode();
+            }
+        } else if (mDeviceState.isSwipeToNotificationEnabled()) {
+            SystemUiProxy.INSTANCE.get(mContext).expandNotificationPanel();
+        }
+    }
+
+    private void onStopGestureDetected() {
+        if (!mDeviceState.isOneHandedModeEnabled() || !mDeviceState.isOneHandedModeActive()) {
+            return;
+        }
+
+        SystemUiProxy.INSTANCE.get(mContext).stopOneHandedMode();
+    }
+
+    private boolean isValidStartAngle(float deltaX, float deltaY) {
+        final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
+        return angle > -(ANGLE_MAX) && angle < -(ANGLE_MIN);
+    }
+
+    private boolean isValidExitAngle(float deltaX, float deltaY) {
+        final float angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX));
+        return angle > ANGLE_MIN && angle < ANGLE_MAX;
+    }
+}
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 9d70316..6737c5f 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -76,6 +76,10 @@
     <dimen name="gestures_assistant_drag_threshold">55dp</dimen>
     <dimen name="gestures_assistant_fling_threshold">55dp</dimen>
 
+    <!-- One-Handed Mode -->
+    <!-- Threshold for draging distance to enable one-handed mode -->
+    <dimen name="gestures_onehanded_drag_threshold">20dp</dimen>
+
     <!-- Distance to move elements when swiping up to go home from launcher -->
     <dimen name="home_pullback_distance">28dp</dimen>
 
diff --git a/quickstep/src/com/android/quickstep/InputConsumer.java b/quickstep/src/com/android/quickstep/InputConsumer.java
index ec720d5..67711c0 100644
--- a/quickstep/src/com/android/quickstep/InputConsumer.java
+++ b/quickstep/src/com/android/quickstep/InputConsumer.java
@@ -35,6 +35,7 @@
     int TYPE_RESET_GESTURE = 1 << 8;
     int TYPE_OVERSCROLL = 1 << 9;
     int TYPE_SYSUI_OVERLAY = 1 << 10;
+    int TYPE_ONE_HANDED = 1 << 11;
 
     String[] NAMES = new String[] {
            "TYPE_NO_OP",                    // 0
@@ -47,7 +48,8 @@
             "TYPE_OVERVIEW_WITHOUT_FOCUS",  // 7
             "TYPE_RESET_GESTURE",           // 8
             "TYPE_OVERSCROLL",              // 9
-            "TYPE_SYSUI_OVERLAY"         // 10
+            "TYPE_SYSUI_OVERLAY",           // 10
+            "TYPE_ONE_HANDED",              // 11
     };
 
     InputConsumer NO_OP = () -> TYPE_NO_OP;
diff --git a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
index 1081548..0710a0a 100644
--- a/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
+++ b/quickstep/src/com/android/quickstep/OrientationTouchTransformer.java
@@ -63,7 +63,9 @@
     private SparseArray<OrientationRectF> mSwipeTouchRegions = new SparseArray<>(MAX_ORIENTATIONS);
     private final RectF mAssistantLeftRegion = new RectF();
     private final RectF mAssistantRightRegion = new RectF();
+    private final RectF mOneHandedModeRegion = new RectF();
     private int mCurrentDisplayRotation;
+    private int mNavBarGesturalHeight;
     private boolean mEnableMultipleRegions;
     private Resources mResources;
     private OrientationRectF mLastRectTouched;
@@ -103,18 +105,33 @@
         mResources = resources;
         mMode = mode;
         mContractInfo = contractInfo;
+        mNavBarGesturalHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
     }
 
-    void setNavigationMode(SysUINavigationMode.Mode newMode, DefaultDisplay.Info info) {
+    private void refreshTouchRegion(DefaultDisplay.Info info, Resources newRes) {
+        // Swipe touch regions are independent of nav mode, so we have to clear them explicitly
+        // here to avoid, for ex, a nav region for 2-button rotation 0 being used for 3-button mode
+        // It tries to cache and reuse swipe regions whenever possible based only on rotation
+        mResources = newRes;
+        mSwipeTouchRegions.clear();
+        resetSwipeRegions(info);
+    }
+
+    void setNavigationMode(SysUINavigationMode.Mode newMode, DefaultDisplay.Info info,
+            Resources newRes) {
         if (mMode == newMode) {
             return;
         }
         this.mMode = newMode;
-        // Swipe touch regions are independent of nav mode, so we have to clear them explicitly
-        // here to avoid, for ex, a nav region for 2-button rotation 0 being used for 3-button mode
-        // It tries to cache and reuse swipe regions whenever possible based only on rotation
-        mSwipeTouchRegions.clear();
-        resetSwipeRegions(info);
+        refreshTouchRegion(info, newRes);
+    }
+
+    void setGesturalHeight(int newGesturalHeight, DefaultDisplay.Info info, Resources newRes) {
+        if (mNavBarGesturalHeight == newGesturalHeight) {
+            return;
+        }
+        mNavBarGesturalHeight = newGesturalHeight;
+        refreshTouchRegion(info, newRes);
     }
 
     /**
@@ -216,10 +233,10 @@
 
         Point size = display.realSize;
         int rotation = display.rotation;
+        int touchHeight = mNavBarGesturalHeight;
         OrientationRectF orientationRectF =
                 new OrientationRectF(0, 0, size.x, size.y, rotation);
         if (mMode == SysUINavigationMode.Mode.NO_BUTTON) {
-            int touchHeight = getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
             orientationRectF.top = orientationRectF.bottom - touchHeight;
             updateAssistantRegions(orientationRectF);
         } else {
@@ -235,10 +252,11 @@
                             + getNavbarSize(ResourceUtils.NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE);
                     break;
                 default:
-                    orientationRectF.top = orientationRectF.bottom
-                            - getNavbarSize(ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE);
+                    orientationRectF.top = orientationRectF.bottom - touchHeight;
             }
         }
+        // One handed gestural only active on portrait mode
+        mOneHandedModeRegion.set(0, orientationRectF.bottom - touchHeight, size.x, size.y);
 
         return orientationRectF;
     }
@@ -264,6 +282,10 @@
 
     }
 
+    boolean touchInOneHandedModeRegion(MotionEvent ev) {
+        return mOneHandedModeRegion.contains(ev.getX(), ev.getY());
+    }
+
     private int getNavbarSize(String resName) {
         return ResourceUtils.getNavbarSize(resName, mResources);
     }
@@ -355,6 +377,8 @@
             regions.append(rectF.mRotation).append(" ");
         }
         pw.println(regions.toString());
+        pw.println("  mNavBarGesturalHeight=" + mNavBarGesturalHeight);
+        pw.println("  mOneHandedModeRegion=" + mOneHandedModeRegion);
     }
 
     private class OrientationRectF extends RectF {
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 7a6bbb4..2868b1b 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -29,6 +29,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
@@ -43,10 +44,13 @@
 import android.content.res.Resources;
 import android.graphics.Region;
 import android.os.Process;
+import android.os.SystemProperties;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.text.TextUtils;
+import android.util.DisplayMetrics;
 import android.view.MotionEvent;
+import android.view.Surface;
 
 import androidx.annotation.BinderThread;
 
@@ -55,6 +59,7 @@
 import com.android.launcher3.util.DefaultDisplay;
 import com.android.launcher3.util.SecureSettingsObserver;
 import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener;
+import com.android.quickstep.SysUINavigationMode.OneHandedModeChangeListener;
 import com.android.quickstep.util.NavBarPosition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -71,7 +76,8 @@
  */
 public class RecentsAnimationDeviceState implements
         NavigationModeChangeListener,
-        DefaultDisplay.DisplayInfoChangeListener {
+        DefaultDisplay.DisplayInfoChangeListener,
+        OneHandedModeChangeListener {
 
     private final Context mContext;
     private final SysUINavigationMode mSysUiNavMode;
@@ -88,6 +94,8 @@
     private final Region mDeferredGestureRegion = new Region();
     private boolean mAssistantAvailable;
     private float mAssistantVisibility;
+    private boolean mIsOneHandedModeEnabled;
+    private boolean mIsSwipeToNotificationEnabled;
 
     private boolean mIsUserUnlocked;
     private final ArrayList<Runnable> mUserUnlockedActions = new ArrayList<>();
@@ -157,6 +165,22 @@
             }
         }
 
+        if (SystemProperties.getBoolean("ro.support_one_handed_mode", false)) {
+            SecureSettingsObserver oneHandedEnabledObserver =
+                    SecureSettingsObserver.newOneHandedSettingsObserver(
+                            mContext, enabled -> mIsOneHandedModeEnabled = enabled);
+            oneHandedEnabledObserver.register();
+            oneHandedEnabledObserver.dispatchOnChange();
+            runOnDestroy(oneHandedEnabledObserver::unregister);
+        }
+
+        SecureSettingsObserver swipeBottomEnabledObserver =
+                SecureSettingsObserver.newSwipeToNotificationSettingsObserver(
+                        mContext, enabled -> mIsSwipeToNotificationEnabled = enabled);
+        swipeBottomEnabledObserver.register();
+        swipeBottomEnabledObserver.dispatchOnChange();
+        runOnDestroy(swipeBottomEnabledObserver::unregister);
+
         SecureSettingsObserver userSetupObserver = new SecureSettingsObserver(
                 context.getContentResolver(),
                 e -> mIsUserSetupComplete = e,
@@ -191,6 +215,15 @@
         runOnDestroy(() -> mSysUiNavMode.removeModeChangeListener(listener));
     }
 
+    /**
+     * Adds a listener for the one handed mode change,
+     * guaranteed to be called after the device state's mode has changed.
+     */
+    public void addOneHandedModeChangedCallback(OneHandedModeChangeListener listener) {
+        listener.onOneHandedModeChanged(mSysUiNavMode.addOneHandedOverlayChangeListener(listener));
+        runOnDestroy(() -> mSysUiNavMode.removeOneHandedOverlayChangeListener(listener));
+    }
+
     @Override
     public void onNavigationModeChanged(SysUINavigationMode.Mode newMode) {
         mDefaultDisplay.removeChangeListener(this);
@@ -204,7 +237,6 @@
         }
 
         mNavBarPosition = new NavBarPosition(newMode, mDefaultDisplay.getInfo());
-
         mMode = newMode;
     }
 
@@ -221,6 +253,11 @@
         mNavBarPosition = new NavBarPosition(mMode, info);
     }
 
+    @Override
+    public void onOneHandedModeChanged(int newGesturalHeight) {
+        mRotationTouchHelper.setGesturalHeight(newGesturalHeight);
+    }
+
     /**
      * @return the current navigation mode for the device.
      */
@@ -406,6 +443,13 @@
     }
 
     /**
+     * @return whether screen pinning is enabled and active
+     */
+    public boolean isOneHandedModeActive() {
+        return (mSystemUiStateFlags & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0;
+    }
+
+    /**
      * Sets the region in screen space where the gestures should be deferred (ie. due to specific
      * nav bar ui).
      */
@@ -467,6 +511,32 @@
                 && !isGestureBlockedActivity(task);
     }
 
+    /**
+     * One handed gestural in quickstep only active on NO_BUTTON, TWO_BUTTONS, and portrait mode
+     *
+     * @param ev The touch screen motion event.
+     * @return whether the given motion event can trigger the one handed mode.
+     */
+    public boolean canTriggerOneHandedAction(MotionEvent ev) {
+        if (!mIsOneHandedModeEnabled && !mIsSwipeToNotificationEnabled) {
+            return false;
+        }
+
+        final DefaultDisplay.Info displayInfo = mDefaultDisplay.getInfo();
+        return (mRotationTouchHelper.touchInOneHandedModeRegion(ev)
+            && displayInfo.rotation != Surface.ROTATION_90
+            && displayInfo.rotation != Surface.ROTATION_270
+            && displayInfo.metrics.densityDpi < DisplayMetrics.DENSITY_600);
+    }
+
+    public boolean isOneHandedModeEnabled() {
+        return mIsOneHandedModeEnabled;
+    }
+
+    public boolean isSwipeToNotificationEnabled() {
+        return mIsSwipeToNotificationEnabled;
+    }
+
     public RotationTouchHelper getRotationTouchHelper() {
         return mRotationTouchHelper;
     }
@@ -481,6 +551,8 @@
         pw.println("  assistantDisabled="
                 + QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
         pw.println("  isUserUnlocked=" + mIsUserUnlocked);
+        pw.println("  isOneHandedModeEnabled=" + mIsOneHandedModeEnabled);
+        pw.println("  isSwipeToNotificationEnabled=" + mIsSwipeToNotificationEnabled);
         mRotationTouchHelper.dump(pw);
     }
 }
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index 5f3c022..2b5e42a 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -188,6 +188,10 @@
         return mOrientationTouchTransformer.touchInAssistantRegion(ev);
     }
 
+    public boolean touchInOneHandedModeRegion(MotionEvent ev) {
+        return mOrientationTouchTransformer.touchInOneHandedModeRegion(ev);
+    }
+
     /**
      * Updates the regions for detecting the swipe up/quickswitch and assistant gestures.
      */
@@ -222,7 +226,8 @@
         mDefaultDisplay.addChangeListener(this);
         onDisplayInfoChanged(mDefaultDisplay.getInfo(), CHANGE_ALL);
 
-        mOrientationTouchTransformer.setNavigationMode(newMode, mDefaultDisplay.getInfo());
+        mOrientationTouchTransformer.setNavigationMode(newMode, mDefaultDisplay.getInfo(),
+            mContext.getResources());
         if (!mMode.hasGestures && newMode.hasGestures) {
             setupOrientationSwipeHandler();
         } else if (mMode.hasGestures && !newMode.hasGestures){
@@ -267,6 +272,14 @@
     }
 
     /**
+     * Sets the gestural height.
+     */
+    void setGesturalHeight(int newGesturalHeight) {
+        mOrientationTouchTransformer.setGesturalHeight(newGesturalHeight, mDefaultDisplay.getInfo(),
+            mContext.getResources());
+    }
+
+    /**
      * *May* apply a transform on the motion event if it lies in the nav bar region for another
      * orientation that is currently being tracked as a part of quickstep
      */
diff --git a/quickstep/src/com/android/quickstep/SysUINavigationMode.java b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
index 05ce2a2..6994d66 100644
--- a/quickstep/src/com/android/quickstep/SysUINavigationMode.java
+++ b/quickstep/src/com/android/quickstep/SysUINavigationMode.java
@@ -16,14 +16,15 @@
 
 package com.android.quickstep;
 
+import static com.android.launcher3.ResourceUtils.INVALID_RESOURCE_HANDLE;
 import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Resources;
 import android.util.Log;
 
+import com.android.launcher3.ResourceUtils;
 import com.android.launcher3.touch.PagedOrientationHandler;
 import com.android.launcher3.util.MainThreadInitializedObject;
 
@@ -58,15 +59,19 @@
             new MainThreadInitializedObject<>(SysUINavigationMode::new);
 
     private static final String TAG = "SysUINavigationMode";
-
     private static final String ACTION_OVERLAY_CHANGED = "android.intent.action.OVERLAY_CHANGED";
     private static final String NAV_BAR_INTERACTION_MODE_RES_NAME =
             "config_navBarInteractionMode";
+    private static final String TARGET_OVERLAY_PACKAGE = "android";
 
     private final Context mContext;
     private Mode mMode;
 
+    private int mNavBarGesturalHeight;
+
     private final List<NavigationModeChangeListener> mChangeListeners = new ArrayList<>();
+    private final List<OneHandedModeChangeListener> mOneHandedOverlayChangeListeners =
+            new ArrayList<>();
 
     public SysUINavigationMode(Context context) {
         mContext = context;
@@ -76,8 +81,9 @@
             @Override
             public void onReceive(Context context, Intent intent) {
                 updateMode();
+                updateGesturalHeight();
             }
-        }, getPackageFilter("android", ACTION_OVERLAY_CHANGED));
+        }, getPackageFilter(TARGET_OVERLAY_PACKAGE, ACTION_OVERLAY_CHANGED));
     }
 
     /** Updates navigation mode when needed. */
@@ -89,9 +95,35 @@
         }
     }
 
+    private void updateGesturalHeight() {
+        int newGesturalHeight = ResourceUtils.getDimenByName(
+                ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mContext.getResources(),
+                INVALID_RESOURCE_HANDLE);
+
+        if (newGesturalHeight == INVALID_RESOURCE_HANDLE) {
+            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+            return;
+        }
+
+        if (mNavBarGesturalHeight != newGesturalHeight) {
+            mNavBarGesturalHeight = newGesturalHeight;
+            dispatchOneHandedOverlayChange();
+        }
+    }
+
     private void initializeMode() {
-        int modeInt = getSystemIntegerRes(mContext, NAV_BAR_INTERACTION_MODE_RES_NAME);
-        for(Mode m : Mode.values()) {
+        int modeInt = ResourceUtils.getIntegerByName(NAV_BAR_INTERACTION_MODE_RES_NAME,
+                mContext.getResources(), INVALID_RESOURCE_HANDLE);
+        mNavBarGesturalHeight = ResourceUtils.getDimenByName(
+                ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE, mContext.getResources(),
+                INVALID_RESOURCE_HANDLE);
+
+        if (modeInt == INVALID_RESOURCE_HANDLE) {
+            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
+            return;
+        }
+
+        for (Mode m : Mode.values()) {
             if (m.resValue == modeInt) {
                 mMode = m;
             }
@@ -104,6 +136,12 @@
         }
     }
 
+    private void dispatchOneHandedOverlayChange() {
+        for (OneHandedModeChangeListener listener : mOneHandedOverlayChangeListeners) {
+            listener.onOneHandedModeChanged(mNavBarGesturalHeight);
+        }
+    }
+
     public Mode addModeChangeListener(NavigationModeChangeListener listener) {
         mChangeListeners.add(listener);
         return mMode;
@@ -113,20 +151,17 @@
         mChangeListeners.remove(listener);
     }
 
-    public Mode getMode() {
-        return mMode;
+    public int addOneHandedOverlayChangeListener(OneHandedModeChangeListener listener) {
+        mOneHandedOverlayChangeListeners.add(listener);
+        return mNavBarGesturalHeight;
     }
 
-    private static int getSystemIntegerRes(Context context, String resName) {
-        Resources res = context.getResources();
-        int resId = res.getIdentifier(resName, "integer", "android");
+    public void removeOneHandedOverlayChangeListener(OneHandedModeChangeListener listener) {
+        mOneHandedOverlayChangeListeners.remove(listener);
+    }
 
-        if (resId != 0) {
-            return res.getInteger(resId);
-        } else {
-            Log.e(TAG, "Failed to get system resource ID. Incompatible framework version?");
-            return -1;
-        }
+    public Mode getMode() {
+        return mMode;
     }
 
     /** @return Whether we can remove the shelf from overview. */
@@ -144,10 +179,16 @@
     public void dump(PrintWriter pw) {
         pw.println("SysUINavigationMode:");
         pw.println("  mode=" + mMode.name());
+        pw.println("  mNavBarGesturalHeight=:" + mNavBarGesturalHeight);
     }
 
     public interface NavigationModeChangeListener {
 
         void onNavigationModeChanged(Mode newMode);
     }
+
+    public interface OneHandedModeChangeListener {
+
+        void onOneHandedModeChanged(int newGesturalHeight);
+    }
 }
\ No newline at end of file
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 299e9e5..e4b05ae 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -352,10 +352,43 @@
         if (mSystemUiProxy != null) {
             try {
                 mSystemUiProxy.handleImageBundleAsScreenshot(screenImageBundle, locationInScreen,
-                        visibleInsets, task);
+                    visibleInsets, task);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed call handleImageBundleAsScreenshot");
             }
         }
     }
+
+    @Override
+    public void startOneHandedMode() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.startOneHandedMode();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call startOneHandedMode", e);
+            }
+        }
+    }
+
+    @Override
+    public void stopOneHandedMode() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.stopOneHandedMode();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call stopOneHandedMode", e);
+            }
+        }
+    }
+
+    @Override
+    public void expandNotificationPanel() {
+        if (mSystemUiProxy != null) {
+            try {
+                mSystemUiProxy.expandNotificationPanel();
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed call expandNotificationPanel", e);
+            }
+        }
+    }
 }
diff --git a/src/com/android/launcher3/ResourceUtils.java b/src/com/android/launcher3/ResourceUtils.java
index 403d779..c9fb75a 100644
--- a/src/com/android/launcher3/ResourceUtils.java
+++ b/src/com/android/launcher3/ResourceUtils.java
@@ -22,6 +22,7 @@
 
 public class ResourceUtils {
     public static final int DEFAULT_NAVBAR_VALUE = 48;
+    public static final int INVALID_RESOURCE_HANDLE = -1;
     public static final String NAVBAR_LANDSCAPE_LEFT_RIGHT_SIZE = "navigation_bar_width";
     public static final String NAVBAR_BOTTOM_GESTURE_SIZE = "navigation_bar_gesture_height";
 
@@ -51,7 +52,13 @@
         return val;
     }
 
+    public static int getIntegerByName(String resName, Resources res, int defaultValue) {
+        int resId = res.getIdentifier(resName, "integer", "android");
+        return resId != 0 ? res.getInteger(resId) : defaultValue;
+    }
+
     public static int pxFromDp(float size, DisplayMetrics metrics) {
-        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics));
+        return size < 0 ? INVALID_RESOURCE_HANDLE : Math.round(
+                TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, metrics));
     }
 }
diff --git a/src/com/android/launcher3/util/SecureSettingsObserver.java b/src/com/android/launcher3/util/SecureSettingsObserver.java
index 48aa02b..4b22429 100644
--- a/src/com/android/launcher3/util/SecureSettingsObserver.java
+++ b/src/com/android/launcher3/util/SecureSettingsObserver.java
@@ -29,6 +29,11 @@
 
     /** Hidden field Settings.Secure.NOTIFICATION_BADGING */
     public static final String NOTIFICATION_BADGING = "notification_badging";
+    /** Hidden field Settings.Secure.ONE_HANDED_MODE_ENABLED */
+    public static final String ONE_HANDED_ENABLED = "one_handed_mode_enabled";
+    /** Hidden field Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED */
+    public static final String ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED =
+            "swipe_bottom_to_notification_enabled";
 
     private final ContentResolver mResolver;
     private final String mKeySetting;
@@ -79,4 +84,21 @@
         return new SecureSettingsObserver(
                 context.getContentResolver(), listener, NOTIFICATION_BADGING, 1);
     }
+
+    public static SecureSettingsObserver newOneHandedSettingsObserver(Context context,
+            OnChangeListener listener) {
+        return new SecureSettingsObserver(
+                context.getContentResolver(), listener, ONE_HANDED_ENABLED, 1);
+    }
+
+    /**
+     * Constructs settings observer for {@link #ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED}
+     * preference.
+     */
+    public static SecureSettingsObserver newSwipeToNotificationSettingsObserver(Context context,
+            OnChangeListener listener) {
+        return new SecureSettingsObserver(
+                context.getContentResolver(), listener,
+                ONE_HANDED_SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 1);
+    }
 }