Enable expanding of status bar just below the notch

Test: Expand status bar with cutout emulation enabled
Bug: 79435224
Change-Id: I427f382578f4dc2abe347973323bb455cb67594d
Merged-In: I427f382578f4dc2abe347973323bb455cb67594d
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f00957a..3b58b72 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1012,4 +1012,7 @@
     <!-- How much into a DisplayCutout's bounds we can go, on each side -->
     <dimen name="display_cutout_margin_consumption">0px</dimen>
 
+    <!-- How much we expand the touchable region of the status bar below the notch to catch touches
+         that just start below the notch. -->
+    <dimen name="display_cutout_touchable_region_size">12dp</dimen>
 </resources>
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index efaf557..6d5e938 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -560,31 +560,34 @@
                     resolveSizeAndState(mBoundingRect.height(), heightMeasureSpec, 0));
         }
 
-        public static void boundsFromDirection(DisplayCutout displayCutout, int gravity, Rect out) {
+        public static void boundsFromDirection(DisplayCutout displayCutout, int gravity,
+                Rect out) {
+            Region bounds = boundsFromDirection(displayCutout, gravity);
+            out.set(bounds.getBounds());
+            bounds.recycle();
+        }
+
+        public static Region boundsFromDirection(DisplayCutout displayCutout, int gravity) {
             Region bounds = displayCutout.getBounds();
             switch (gravity) {
                 case Gravity.TOP:
                     bounds.op(0, 0, Integer.MAX_VALUE, displayCutout.getSafeInsetTop(),
                             Region.Op.INTERSECT);
-                    out.set(bounds.getBounds());
                     break;
                 case Gravity.LEFT:
                     bounds.op(0, 0, displayCutout.getSafeInsetLeft(), Integer.MAX_VALUE,
                             Region.Op.INTERSECT);
-                    out.set(bounds.getBounds());
                     break;
                 case Gravity.BOTTOM:
                     bounds.op(0, displayCutout.getSafeInsetTop() + 1, Integer.MAX_VALUE,
                             Integer.MAX_VALUE, Region.Op.INTERSECT);
-                    out.set(bounds.getBounds());
                     break;
                 case Gravity.RIGHT:
                     bounds.op(displayCutout.getSafeInsetLeft() + 1, 0, Integer.MAX_VALUE,
                             Integer.MAX_VALUE, Region.Op.INTERSECT);
-                    out.set(bounds.getBounds());
                     break;
             }
-            bounds.recycle();
+            return bounds;
         }
 
         private void localBounds(Rect out) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index fa0a774..0aad438 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -21,15 +21,22 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.Region.Op;
 import android.support.v4.util.ArraySet;
 import android.util.Log;
 import android.util.Pools;
+import android.view.DisplayCutout;
+import android.view.Gravity;
 import android.view.View;
 import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.InternalInsetsInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.ScreenDecorations;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.StatusBarState;
@@ -41,6 +48,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Stack;
 
 /**
@@ -60,6 +68,7 @@
 
     private int mStatusBarHeight;
     private int mHeadsUpInset;
+    private int mDisplayCutoutTouchableRegionSize;
     private boolean mTrackingHeadsUp;
     private HashSet<String> mSwipedOutKeys = new HashSet<>();
     private HashSet<NotificationData.Entry> mEntriesToRemoveAfterExpand = new HashSet<>();
@@ -120,6 +129,8 @@
                 com.android.internal.R.dimen.status_bar_height);
         mHeadsUpInset = mStatusBarHeight + resources.getDimensionPixelSize(
                 R.dimen.heads_up_status_bar_padding);
+        mDisplayCutoutTouchableRegionSize = resources.getDimensionPixelSize(
+                R.dimen.display_cutout_touchable_region_size);
     }
 
     @Override
@@ -128,6 +139,11 @@
         initResources();
     }
 
+    @Override
+    public void onOverlayChanged() {
+        initResources();
+    }
+
     ///////////////////////////////////////////////////////////////////////////////////////////////
     //  Public methods:
 
@@ -301,12 +317,32 @@
 
             info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
             info.touchableRegion.set(minX, 0, maxX, mHeadsUpInset + height);
-        } else if (mHeadsUpGoingAway || mWaitingOnCollapseWhenGoingAway) {
-            info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
-            info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight);
+        } else {
+            setCollapsedTouchableInsets(info);
         }
     }
 
+    private void setCollapsedTouchableInsets(ViewTreeObserver.InternalInsetsInfo info) {
+        info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+        info.touchableRegion.set(0, 0, mStatusBarWindowView.getWidth(), mStatusBarHeight);
+        updateRegionForNotch(info.touchableRegion);
+    }
+
+    private void updateRegionForNotch(Region region) {
+        DisplayCutout cutout = mStatusBarWindowView.getRootWindowInsets().getDisplayCutout();
+        if (cutout == null) {
+            return;
+        }
+
+        // Expand touchable region such that we also catch touches that just start below the notch
+        // area.
+        Region bounds = ScreenDecorations.DisplayCutoutView.boundsFromDirection(
+                cutout, Gravity.TOP);
+        bounds.translate(0, mDisplayCutoutTouchableRegionSize);
+        region.op(bounds, Op.UNION);
+        bounds.recycle();
+    }
+
     @Override
     public void onConfigChanged(Configuration newConfig) {
         Resources resources = mContext.getResources();
@@ -403,7 +439,8 @@
 
     private void updateTouchableRegionListener() {
         boolean shouldObserve = hasPinnedHeadsUp() || mHeadsUpGoingAway
-                || mWaitingOnCollapseWhenGoingAway;
+                || mWaitingOnCollapseWhenGoingAway
+                || mStatusBarWindowView.getRootWindowInsets().getDisplayCutout() != null;
         if (shouldObserve == mIsObserving) {
             return;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index a79a41b..0390f60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -94,6 +94,12 @@
     private boolean mExpandAnimationRunning;
     private boolean mExpandAnimationPending;
 
+    /**
+     * If set to true, the current gesture started below the notch and we need to dispatch touch
+     * events manually as it's outside of the regular view bounds.
+     */
+    private boolean mExpandingBelowNotch;
+
     public StatusBarWindowView(Context context, AttributeSet attrs) {
         super(context, attrs);
         setMotionEventSplittingEnabled(false);
@@ -258,7 +264,16 @@
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN;
+        boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
         boolean isCancel = ev.getActionMasked() == MotionEvent.ACTION_CANCEL;
+
+        // Reset manual touch dispatch state here but make sure the UP/CANCEL event still gets
+        // delivered.
+        boolean expandingBelowNotch = mExpandingBelowNotch;
+        if (isUp || isCancel) {
+            mExpandingBelowNotch = false;
+        }
+
         if (!isCancel && mService.shouldIgnoreTouch()) {
             return false;
         }
@@ -291,6 +306,17 @@
             mService.mDozeScrimController.extendPulse();
         }
 
+        // In case we start outside of the view bounds (below the status bar), we need to dispatch
+        // the touch manually as the view system can't accomodate for touches outside of the
+        // regular view bounds.
+        if (isDown && ev.getY() >= mBottom) {
+            mExpandingBelowNotch = true;
+            expandingBelowNotch = true;
+        }
+        if (expandingBelowNotch) {
+            return mNotificationPanel.dispatchTouchEvent(ev);
+        }
+
         return super.dispatchTouchEvent(ev);
     }