Navigation hideybar via new system ui opt-in flag.

Apps using SYSTEM_UI_FLAG_HIDE_NAVIGATION to hide the nav bar
or SYSTEM_UI_FLAG_FULLSCREEN to hide the status bar can now
opt into hideybars by also using a new public sysui flag:
  View.SYSTEM_UI_FLAG_ALLOW_OVERLAY

When opting in, apps accept the fact that bars can be overlayed
over their content, but gain the ability to use the entire gesture
space - something that was not possible before, particularly when
hiding the nav bar.

Swiping from the nav bar edge of the screen will reveal the new hidey
version of the nav bar, if applicable.

Bug: 8682181
Change-Id: I6405bee50e6516667ba6b9a62d4f1e43490b5562
diff --git a/api/current.txt b/api/current.txt
index 6c52a17..7200dad 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -26447,6 +26447,7 @@
     field public static final int SOUND_EFFECTS_ENABLED = 134217728; // 0x8000000
     field public static final deprecated int STATUS_BAR_HIDDEN = 1; // 0x1
     field public static final deprecated int STATUS_BAR_VISIBLE = 0; // 0x0
+    field public static final int SYSTEM_UI_FLAG_ALLOW_OVERLAY = 2048; // 0x800
     field public static final int SYSTEM_UI_FLAG_FULLSCREEN = 4; // 0x4
     field public static final int SYSTEM_UI_FLAG_HIDE_NAVIGATION = 2; // 0x2
     field public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 1024; // 0x400
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 25c380e..782f817 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2362,6 +2362,15 @@
     public static final int SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN = 0x00000400;
 
     /**
+     * Flag for {@link #setSystemUiVisibility(int)}: View would like to receive touch events
+     * when hiding the status bar with {@link #SYSTEM_UI_FLAG_FULLSCREEN} and/or hiding the
+     * navigation bar with {@link #SYSTEM_UI_FLAG_HIDE_NAVIGATION} instead of having the system
+     * clear these flags upon interaction.  The system may compensate by temporarily overlaying
+     * transparent system ui while also delivering the event.
+     */
+    public static final int SYSTEM_UI_FLAG_ALLOW_OVERLAY = 0x00000800;
+
+    /**
      * @deprecated Use {@link #SYSTEM_UI_FLAG_LOW_PROFILE} instead.
      */
     public static final int STATUS_BAR_HIDDEN = SYSTEM_UI_FLAG_LOW_PROFILE;
@@ -2487,13 +2496,25 @@
      * out of the public fields to keep the undefined bits out of the developer's way.
      *
      * Flag to specify that the status bar should temporarily overlay underlying content
-     * that is otherwise assuming the status bar is hidden.  The status bar will typically
+     * that is otherwise assuming the status bar is hidden.  The status bar may
      * have some degree of transparency while in this temporary overlay mode.
      */
     public static final int STATUS_BAR_OVERLAY = 0x04000000;
 
     /**
      * @hide
+     *
+     * NOTE: This flag may only be used in subtreeSystemUiVisibility. It is masked
+     * out of the public fields to keep the undefined bits out of the developer's way.
+     *
+     * Flag to specify that the navigation bar should temporarily overlay underlying content
+     * that is otherwise assuming the navigation bar is hidden.  The navigation bar mayu
+     * have some degree of transparency while in this temporary overlay mode.
+     */
+    public static final int NAVIGATION_BAR_OVERLAY = 0x08000000;
+
+    /**
+     * @hide
      */
     public static final int PUBLIC_STATUS_BAR_VISIBILITY_MASK = 0x0000FFFF;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 683824b..af72936 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -1172,4 +1172,12 @@
         KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
         return km.inKeyguardRestrictedInputMode();
     }
+
+    public void suspendAutohide() {
+        // hook for subclasses
+    }
+
+    public void resumeAutohide() {
+        // hook for subclasses
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java
index 3ac1bcf..9648b11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DelegateViewHelper.java
@@ -57,8 +57,8 @@
         final float sourceX = mTempPoint[0];
         final float sourceY = mTempPoint[1];
 
-
-        switch (event.getAction()) {
+        final int action = event.getAction();
+        switch (action) {
             case MotionEvent.ACTION_DOWN:
                 mPanelShowing = mDelegateView.getVisibility() == View.VISIBLE;
                 mDownPoint[0] = event.getX();
@@ -71,7 +71,7 @@
             return false;
         }
 
-        if (!mPanelShowing && event.getAction() == MotionEvent.ACTION_MOVE) {
+        if (!mPanelShowing && action == MotionEvent.ACTION_MOVE) {
             final int historySize = event.getHistorySize();
             for (int k = 0; k < historySize + 1; k++) {
                 float x = k < historySize ? event.getHistoricalX(k) : event.getX();
@@ -85,6 +85,12 @@
             }
         }
 
+        if (action == MotionEvent.ACTION_DOWN) {
+            mBar.suspendAutohide();
+        } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
+            mBar.resumeAutohide();
+        }
+
         mDelegateView.getLocationOnScreen(mTempPoint);
         final float delegateX = mTempPoint[0];
         final float delegateY = mTempPoint[1];
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 82a5012..e8d4e1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -132,6 +132,8 @@
     private static final int NOTIFICATION_PRIORITY_MULTIPLIER = 10; // see NotificationManagerService
     private static final int HIDE_ICONS_BELOW_SCORE = Notification.PRIORITY_LOW * NOTIFICATION_PRIORITY_MULTIPLIER;
 
+    private static final int STATUS_OR_NAV_OVERLAY =
+            View.STATUS_BAR_OVERLAY | View.NAVIGATION_BAR_OVERLAY;
     private static final long AUTOHIDE_TIMEOUT_MS = 3000;
     private static final float TRANSPARENT_ALPHA = 0.7f;
 
@@ -312,7 +314,7 @@
     private final Runnable mAutohide = new Runnable() {
         @Override
         public void run() {
-            int requested = mSystemUiVisibility & ~View.STATUS_BAR_OVERLAY;
+            int requested = mSystemUiVisibility & ~STATUS_OR_NAV_OVERLAY;
             notifyUiVisibilityChanged(requested);
         }};
 
@@ -806,7 +808,7 @@
                     | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                     | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                     | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH,
-                PixelFormat.OPAQUE);
+                PixelFormat.TRANSLUCENT);
         // this will allow the navbar to run in an overlay on devices that support this
         if (ActivityManager.isHighEndGfx()) {
             lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
@@ -1682,9 +1684,7 @@
         }
 
         // Reschedule suspended auto-hide if necessary
-        if (mAutohideSuspended) {
-            scheduleAutohide();
-        }
+        resumeAutohide();
     }
 
     /**
@@ -1875,13 +1875,20 @@
                 setStatusBarLowProfile(lightsOut);
             }
 
-            if (0 != (diff & View.STATUS_BAR_OVERLAY)) {
-                boolean overlay = 0 != (vis & View.STATUS_BAR_OVERLAY);
-                if (overlay) {
-                    setTransparent(true);
+            boolean sbOverlayChanged = 0 != (diff & View.STATUS_BAR_OVERLAY);
+            boolean nbOverlayChanged = 0 != (diff & View.NAVIGATION_BAR_OVERLAY);
+            if (sbOverlayChanged || nbOverlayChanged) {
+                boolean sbOverlay = 0 != (vis & View.STATUS_BAR_OVERLAY);
+                boolean nbOverlay = 0 != (vis & View.NAVIGATION_BAR_OVERLAY);
+                if (sbOverlayChanged) {
+                    setTransparent(mStatusBarView, sbOverlay);
+                }
+                if (nbOverlayChanged) {
+                    setTransparent(mNavigationBarView, nbOverlay);
+                }
+                if (sbOverlayChanged && sbOverlay || nbOverlayChanged && nbOverlay) {
                     scheduleAutohide();
                 } else {
-                    setTransparent(false);
                     cancelAutohide();
                 }
             }
@@ -1889,9 +1896,17 @@
         }
     }
 
-    private void suspendAutohide() {
+    @Override
+    public void resumeAutohide() {
+        if (mAutohideSuspended) {
+            scheduleAutohide();
+        }
+    }
+
+    @Override
+    public void suspendAutohide() {
         mHandler.removeCallbacks(mAutohide);
-        mAutohideSuspended = (0 != (mSystemUiVisibility & View.STATUS_BAR_OVERLAY));
+        mAutohideSuspended = 0 != (mSystemUiVisibility & STATUS_OR_NAV_OVERLAY);
     }
 
     private void cancelAutohide() {
@@ -1904,10 +1919,11 @@
         mHandler.postDelayed(mAutohide, AUTOHIDE_TIMEOUT_MS);
     }
 
-    private void setTransparent(boolean transparent) {
+    private void setTransparent(View view, boolean transparent) {
         float alpha = transparent ? TRANSPARENT_ALPHA : 1;
-        if (DEBUG) Slog.d(TAG, "Setting alpha to " + alpha);
-        mStatusBarView.setAlpha(alpha);
+        if (DEBUG) Slog.d(TAG, "Setting " + (view == mStatusBarView ? "status bar" :
+                view == mNavigationBarView ? "navigation bar" : "view") +  " alpha to " + alpha);
+        view.setAlpha(alpha);
     }
 
     private void setStatusBarLowProfile(boolean lightsOut) {
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 2b0e27a..c81f863 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -103,6 +103,7 @@
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.HashSet;
 
 import static android.view.WindowManager.LayoutParams.*;
 import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
@@ -552,13 +553,14 @@
     }
     MyOrientationListener mOrientationListener;
 
-    private static final int HIDEYBARS_NONE = 0;
-    private static final int HIDEYBARS_SHOWING = 1;
-    private static final int HIDEYBARS_HIDING = 2;
-    private int mHideybars;
+    private static final int HIDEYBAR_NONE = 0;
+    private static final int HIDEYBAR_SHOWING = 1;
+    private static final int HIDEYBAR_HIDING = 2;
+    private int mStatusHideybar;
+    private int mNavigationHideybar;
 
     private InputChannel mSystemGestureInputChannel;
-    private InputEventReceiver mSystemGestures;
+    private SystemGestures mSystemGestures;
 
     IStatusBarService getStatusBarService() {
         synchronized (mServiceAquireLock) {
@@ -919,8 +921,27 @@
                 new SystemGestures.Callbacks() {
                     @Override
                     public void onSwipeFromTop() {
-                        showHideybars();
-                    }});
+                        showHideybars(mStatusBar);
+                    }
+                    @Override
+                    public void onSwipeFromBottom() {
+                        if (mNavigationBarOnBottom) {
+                            showHideybars(mNavigationBar);
+                        }
+                    }
+                    @Override
+                    public void onSwipeFromRight() {
+                        if (!mNavigationBarOnBottom) {
+                            showHideybars(mNavigationBar);
+                        }
+                    }
+                    @Override
+                    public void onDebug() {
+                        if (OverlayTesting.ENABLED) {
+                            OverlayTesting.toggleForceOverlay(mFocusedWindow, mContext);
+                        }
+                    }
+                });
 
         mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
         mLongPressVibePattern = getLongIntArray(mContext.getResources(),
@@ -2500,10 +2521,16 @@
 
     @Override
     public int adjustSystemUiVisibilityLw(int visibility) {
-        if (mHideybars == HIDEYBARS_SHOWING && 0 == (visibility & View.STATUS_BAR_OVERLAY)) {
-            mHideybars = HIDEYBARS_HIDING;
+        if (mStatusHideybar == HIDEYBAR_SHOWING &&
+                0 == (visibility & View.STATUS_BAR_OVERLAY)) {
+            mStatusHideybar = HIDEYBAR_HIDING;
             mStatusBar.hideLw(true);
         }
+        if (mNavigationHideybar == HIDEYBAR_SHOWING &&
+                0 == (visibility & View.NAVIGATION_BAR_OVERLAY)) {
+            mNavigationHideybar = HIDEYBAR_HIDING;
+            mNavigationBar.hideLw(true);
+        }
         // Reset any bits in mForceClearingStatusBarVisibility that
         // are now clear.
         mResettingSystemUiFlags &= visibility;
@@ -2605,8 +2632,8 @@
         mUnrestrictedScreenHeight = displayHeight - overscanTop - overscanBottom;
         mRestrictedScreenLeft = mUnrestrictedScreenLeft;
         mRestrictedScreenTop = mUnrestrictedScreenTop;
-        mRestrictedScreenWidth = mUnrestrictedScreenWidth;
-        mRestrictedScreenHeight = mUnrestrictedScreenHeight;
+        mRestrictedScreenWidth = mSystemGestures.screenWidth = mUnrestrictedScreenWidth;
+        mRestrictedScreenHeight = mSystemGestures.screenHeight = mUnrestrictedScreenHeight;
         mDockLeft = mContentLeft = mStableLeft = mStableFullscreenLeft
                 = mCurLeft = mUnrestrictedScreenLeft;
         mDockTop = mContentTop = mStableTop = mStableFullscreenTop
@@ -2632,12 +2659,13 @@
             // For purposes of putting out fake window up to steal focus, we will
             // drive nav being hidden only by whether it is requested.
             boolean navVisible = (mLastSystemUiFlags&View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
+            boolean overlayAllowed = (mLastSystemUiFlags&View.SYSTEM_UI_FLAG_ALLOW_OVERLAY) != 0;
 
             // When the navigation bar isn't visible, we put up a fake
             // input window to catch all touch events.  This way we can
             // detect when the user presses anywhere to bring back the nav
             // bar and ensure the application doesn't see the event.
-            if (navVisible) {
+            if (navVisible || overlayAllowed) {
                 if (mHideNavFakeWindow != null) {
                     mHideNavFakeWindow.dismiss();
                     mHideNavFakeWindow = null;
@@ -2654,7 +2682,9 @@
             // then take that into account.
             navVisible |= !mCanHideNavigationBar;
 
+            boolean updateSysUiVisibility = false;
             if (mNavigationBar != null) {
+                boolean navBarHideyShowing = mNavigationHideybar == HIDEYBAR_SHOWING;
                 // Force the navigation bar to its appropriate place and
                 // size.  We need to do this directly, instead of relying on
                 // it to bubble up from the nav bar, because this needs to
@@ -2666,7 +2696,9 @@
                             - mNavigationBarHeightForRotation[displayRotation];
                     mTmpNavigationFrame.set(0, top, displayWidth, displayHeight - overscanBottom);
                     mStableBottom = mStableFullscreenBottom = mTmpNavigationFrame.top;
-                    if (navVisible) {
+                    if (navBarHideyShowing) {
+                        mNavigationBar.showLw(true);
+                    } else if (navVisible) {
                         mNavigationBar.showLw(true);
                         mDockBottom = mTmpNavigationFrame.top;
                         mRestrictedScreenHeight = mDockBottom - mRestrictedScreenTop;
@@ -2687,7 +2719,9 @@
                             - mNavigationBarWidthForRotation[displayRotation];
                     mTmpNavigationFrame.set(left, 0, displayWidth - overscanRight, displayHeight);
                     mStableRight = mStableFullscreenRight = mTmpNavigationFrame.left;
-                    if (navVisible) {
+                    if (navBarHideyShowing) {
+                        mNavigationBar.showLw(true);
+                    } else if (navVisible) {
                         mNavigationBar.showLw(true);
                         mDockRight = mTmpNavigationFrame.left;
                         mRestrictedScreenWidth = mDockRight - mRestrictedScreenLeft;
@@ -2714,6 +2748,11 @@
                 mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame,
                         mTmpNavigationFrame, mTmpNavigationFrame, mTmpNavigationFrame);
                 if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + mTmpNavigationFrame);
+                if (mNavigationHideybar == HIDEYBAR_HIDING && !mNavigationBar.isVisibleLw()) {
+                    // Finished animating out, clean up and reset alpha
+                    mNavigationHideybar = HIDEYBAR_NONE;
+                    updateSysUiVisibility = true;
+                }
             }
             if (DEBUG_LAYOUT) Slog.i(TAG, String.format("mDock rect: (%d,%d - %d,%d)",
                     mDockLeft, mDockTop, mDockRight, mDockBottom));
@@ -2767,12 +2806,16 @@
                     // we can tell the app that it is covered by it.
                     mSystemTop = mUnrestrictedScreenTop + mStatusBarHeight;
                 }
-                if (mHideybars == HIDEYBARS_HIDING && !mStatusBar.isVisibleLw()) {
-                    // Hideybars have finished animating out, cleanup and reset alpha
-                    mHideybars = HIDEYBARS_NONE;
-                    updateSystemUiVisibilityLw();
+
+                if (mStatusHideybar == HIDEYBAR_HIDING && !mStatusBar.isVisibleLw()) {
+                    // Finished animating out, clean up and reset alpha
+                    mStatusHideybar = HIDEYBAR_NONE;
+                    updateSysUiVisibility = true;
                 }
             }
+            if (updateSysUiVisibility) {
+                updateSystemUiVisibilityLw();
+            }
         }
     }
 
@@ -3347,7 +3390,7 @@
                 // and mTopIsFullscreen is that that mTopIsFullscreen is set only if the window
                 // has the FLAG_FULLSCREEN set.  Not sure if there is another way that to be the
                 // case though.
-                if (mHideybars == HIDEYBARS_SHOWING) {
+                if (mStatusHideybar == HIDEYBAR_SHOWING) {
                     if (mStatusBar.showLw(true)) {
                         changes |= FINISH_LAYOUT_REDO_LAYOUT;
                     }
@@ -4099,18 +4142,32 @@
         }
     };
 
-    private void showHideybars() {
+    private void showHideybars(WindowState swipeTarget) {
         synchronized(mLock) {
-            if (mHideybars == HIDEYBARS_SHOWING) {
-                if (DEBUG) Slog.d(TAG, "Not showing hideybars, already shown");
-                return;
+            boolean sb = checkShowHideybar("status", mStatusHideybar, mStatusBar);
+            boolean nb = checkShowHideybar("navigation", mNavigationHideybar, mNavigationBar);
+            if (sb || nb) {
+                WindowState hideyTarget = sb ? mStatusBar : mNavigationBar;
+                if (sb ^ nb && hideyTarget != swipeTarget) {
+                    if (DEBUG) Slog.d(TAG, "Not showing hideybar, wrong swipe target");
+                    return;
+                }
+                mStatusHideybar = sb ? HIDEYBAR_SHOWING : mStatusHideybar;
+                mNavigationHideybar = nb ? HIDEYBAR_SHOWING : mNavigationHideybar;
+                updateSystemUiVisibilityLw();
             }
-            if (mStatusBar.isDisplayedLw()) {
-                if (DEBUG) Slog.d(TAG, "Not showing hideybars, status bar already visible");
-                return;
-            }
-            mHideybars = HIDEYBARS_SHOWING;
-            updateSystemUiVisibilityLw();
+        }
+    }
+
+    private boolean checkShowHideybar(String tag, int hideybar, WindowState win) {
+        if (hideybar == HIDEYBAR_SHOWING) {
+            if (DEBUG) Slog.d(TAG, "Not showing " + tag + " hideybar, already shown");
+            return false;
+        } else if (win.isDisplayedLw()) {
+            if (DEBUG) Slog.d(TAG, "Not showing " + tag + " hideybar, bar already visible");
+            return false;
+        } else {
+            return true;
         }
     }
 
@@ -4941,31 +4998,8 @@
         if (mForcingShowNavBar && mFocusedWindow.getSurfaceLayer() < mForcingShowNavBarLayer) {
             tmpVisibility &= ~View.SYSTEM_UI_CLEARABLE_FLAGS;
         }
-
-        boolean hideybarsAllowed =
-                (mFocusedWindow.getAttrs().flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0
-                || mFocusedWindow.getAttrs().type == TYPE_STATUS_BAR;
-        if (mHideybars == HIDEYBARS_SHOWING) {
-            if (!hideybarsAllowed) {
-                mHideybars = HIDEYBARS_NONE;
-                if ((tmpVisibility & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0) {
-                    // hideybars for View.SYSTEM_UI_FLAG_FULLSCREEN: clear the clearable flags
-                    int newVal = mResettingSystemUiFlags | View.SYSTEM_UI_CLEARABLE_FLAGS;
-                    if (newVal != mResettingSystemUiFlags) {
-                        mResettingSystemUiFlags = newVal;
-                        mWindowManagerFuncs.reevaluateStatusBarVisibility();
-                    }
-                }
-            } else {
-                // hideybars for WM.LP.FLAG_FULLSCREEN: show transparent status bar
-                tmpVisibility |= View.STATUS_BAR_OVERLAY;
-                if ((mLastSystemUiFlags & View.STATUS_BAR_OVERLAY) == 0) {
-                    mStatusBar.showLw(true);
-                }
-            }
-        }
-        final int visibility = tmpVisibility;
-        int diff = visibility ^ mLastSystemUiFlags;
+        final int visibility = updateHideybarsLw(tmpVisibility);
+        final int diff = visibility ^ mLastSystemUiFlags;
         final boolean needsMenu = mFocusedWindow.getNeedsMenuLw(mTopFullscreenOpaqueWindowState);
         if (diff == 0 && mLastFocusNeedsMenu == needsMenu
                 && mFocusedApp == mFocusedWindow.getAppToken()) {
@@ -4992,6 +5026,115 @@
         return diff;
     }
 
+    private int updateHideybarsLw(int tmpVisibility) {
+        if (OverlayTesting.ENABLED) {
+            tmpVisibility = OverlayTesting.applyForced(mFocusedWindow, tmpVisibility);
+        }
+        boolean statusBarHasFocus =
+                mFocusedWindow.getAttrs().type == TYPE_STATUS_BAR;
+        if (statusBarHasFocus) {
+            // prevent status bar interaction from clearing certain flags
+            int flags = View.SYSTEM_UI_FLAG_FULLSCREEN
+                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                    | View.SYSTEM_UI_FLAG_ALLOW_OVERLAY;
+            tmpVisibility = (tmpVisibility & ~flags) | (mLastSystemUiFlags & flags);
+        }
+        boolean overlayAllowed = (tmpVisibility & View.SYSTEM_UI_FLAG_ALLOW_OVERLAY) != 0;
+        if (mStatusHideybar == HIDEYBAR_SHOWING) {
+            // status hideybar requested
+            boolean hideStatusBarWM =
+                    (mFocusedWindow.getAttrs().flags
+                            & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0;
+            boolean hideStatusBarSysui =
+                    (tmpVisibility & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
+
+            boolean statusHideyAllowed =
+                    hideStatusBarWM
+                    || (hideStatusBarSysui && overlayAllowed)
+                    || statusBarHasFocus;
+
+            if (!statusHideyAllowed) {
+                mStatusHideybar = HIDEYBAR_NONE;
+                if (hideStatusBarSysui) {
+                    // clear the clearable flags instead
+                    int newVal = mResettingSystemUiFlags | View.SYSTEM_UI_CLEARABLE_FLAGS;
+                    if (newVal != mResettingSystemUiFlags) {
+                        mResettingSystemUiFlags = newVal;
+                        mWindowManagerFuncs.reevaluateStatusBarVisibility();
+                    }
+                }
+            } else {
+                // show status hideybar
+                tmpVisibility |= View.STATUS_BAR_OVERLAY;
+                if ((mLastSystemUiFlags & View.STATUS_BAR_OVERLAY) == 0) {
+                    mStatusBar.showLw(true);
+                }
+            }
+        }
+        if (mNavigationHideybar == HIDEYBAR_SHOWING) {
+            // navigation hideybar requested
+            boolean hideNavigationBarSysui =
+                    (tmpVisibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
+            boolean navigationHideyAllowed =
+                    hideNavigationBarSysui && overlayAllowed;
+            if (!navigationHideyAllowed) {
+                mNavigationHideybar = HIDEYBAR_NONE;
+            } else {
+                // show navigation hideybar
+                tmpVisibility |= View.NAVIGATION_BAR_OVERLAY;
+                if ((mLastSystemUiFlags & View.NAVIGATION_BAR_OVERLAY) == 0) {
+                    mNavigationBar.showLw(true);
+                }
+            }
+        }
+        return tmpVisibility;
+    }
+
+    // TODO temporary helper that allows testing overlay bars on existing apps
+    private static final class OverlayTesting {
+        static final boolean ENABLED = true;
+        private static final HashSet<String> sForced = new HashSet<String>();
+
+        private static String parseActivity(WindowState win) {
+            if (win != null && win.getAppToken() != null) {
+                String str = win.getAppToken().toString();
+                int end = str.lastIndexOf(' ');
+                if (end > 0) {
+                    int start = str.lastIndexOf(' ', end - 1);
+                    if (start > -1) {
+                        return str.substring(start + 1, end);
+                    }
+                }
+            }
+            return null;
+        }
+
+        public static int applyForced(WindowState focused, int vis) {
+            if (sForced.contains(parseActivity(focused))) {
+                vis |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
+                       View.SYSTEM_UI_FLAG_FULLSCREEN |
+                       View.SYSTEM_UI_FLAG_ALLOW_OVERLAY;
+            }
+            return vis;
+        }
+
+        public static void toggleForceOverlay(WindowState focused, Context context) {
+            String activity = parseActivity(focused);
+            if (activity != null) {
+                String action;
+                if (sForced.contains(activity)) {
+                    sForced.remove(activity);
+                    action = "Force overlay disabled";
+                } else {
+                    sForced.add(activity);
+                    action = "Force overlay enabled";
+                }
+                android.widget.Toast.makeText(context,
+                        action + " for " + activity, android.widget.Toast.LENGTH_SHORT).show();
+            }
+        }
+    }
+
     // Use this instead of checking config_showNavigationBar so that it can be consistently
     // overridden by qemu.hw.mainkeys in the emulator.
     @Override
diff --git a/policy/src/com/android/internal/policy/impl/SystemGestures.java b/policy/src/com/android/internal/policy/impl/SystemGestures.java
index 083fcbc..c9731a5 100644
--- a/policy/src/com/android/internal/policy/impl/SystemGestures.java
+++ b/policy/src/com/android/internal/policy/impl/SystemGestures.java
@@ -36,15 +36,24 @@
     private static final int MAX_TRACKED_POINTERS = 32;  // max per input system
     private static final int UNTRACKED_POINTER = -1;
 
+    private static final int SWIPE_NONE = 0;
+    private static final int SWIPE_FROM_TOP = 1;
+    private static final int SWIPE_FROM_BOTTOM = 2;
+    private static final int SWIPE_FROM_RIGHT = 3;
+
     private final int mSwipeStartThreshold;
     private final int mSwipeEndThreshold;
     private final Callbacks mCallbacks;
     private final int[] mDownPointerId = new int[MAX_TRACKED_POINTERS];
+    private final float[] mDownX = new float[MAX_TRACKED_POINTERS];
     private final float[] mDownY = new float[MAX_TRACKED_POINTERS];
     private final long[] mDownTime = new long[MAX_TRACKED_POINTERS];
 
+    int screenHeight;
+    int screenWidth;
     private int mDownPointers;
-    private boolean mSwipeFromTopFireable;
+    private boolean mSwipeFireable;
+    private boolean mDebugFireable;
 
     public SystemGestures(InputChannel inputChannel, Looper looper,
             Context context, Callbacks callbacks) {
@@ -73,23 +82,41 @@
     private void onPointerMotionEvent(MotionEvent event) {
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_DOWN:
-                mSwipeFromTopFireable = true;
+                mSwipeFireable = true;
+                mDebugFireable = true;
                 mDownPointers = 0;
                 captureDown(event, 0);
                 break;
             case MotionEvent.ACTION_POINTER_DOWN:
                 captureDown(event, event.getActionIndex());
+                if (mDebugFireable) {
+                    mDebugFireable = event.getPointerCount() < 5;
+                    if (!mDebugFireable) {
+                        if (DEBUG) Slog.d(TAG, "Firing debug");
+                        mCallbacks.onDebug();
+                    }
+                }
                 break;
             case MotionEvent.ACTION_MOVE:
-                if (mSwipeFromTopFireable && detectSwipeFromTop(event)) {
-                    mSwipeFromTopFireable = false;
-                    if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop");
-                    mCallbacks.onSwipeFromTop();
+                if (mSwipeFireable) {
+                    final int swipe = detectSwipe(event);
+                    mSwipeFireable = swipe == SWIPE_NONE;
+                    if (swipe == SWIPE_FROM_TOP) {
+                        if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop");
+                        mCallbacks.onSwipeFromTop();
+                    } else if (swipe == SWIPE_FROM_BOTTOM) {
+                        if (DEBUG) Slog.d(TAG, "Firing onSwipeFromBottom");
+                        mCallbacks.onSwipeFromBottom();
+                    } else if (swipe == SWIPE_FROM_RIGHT) {
+                        if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight");
+                        mCallbacks.onSwipeFromRight();
+                    }
                 }
                 break;
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL:
-                mSwipeFromTopFireable = false;
+                mSwipeFireable = false;
+                mDebugFireable = false;
                 break;
             default:
                 if (DEBUG) Slog.d(TAG, "Ignoring " + event);
@@ -102,9 +129,11 @@
         if (DEBUG) Slog.d(TAG, "pointer " + pointerId +
                 " down pointerIndex=" + pointerIndex + " trackingIndex=" + i);
         if (i != UNTRACKED_POINTER) {
+            mDownX[i] = event.getX(pointerIndex);
             mDownY[i] = event.getY(pointerIndex);
             mDownTime[i] = event.getEventTime();
-            if (DEBUG) Slog.d(TAG, "pointer " + pointerId + " down y=" + mDownY[i]);
+            if (DEBUG) Slog.d(TAG, "pointer " + pointerId +
+                    " down x=" + mDownX[i] + " y=" + mDownY[i]);
         }
     }
 
@@ -121,7 +150,7 @@
         return mDownPointers - 1;
     }
 
-    private boolean detectSwipeFromTop(MotionEvent move) {
+    private int detectSwipe(MotionEvent move) {
         final int historySize = move.getHistorySize();
         final int pointerCount = move.getPointerCount();
         for (int p = 0; p < pointerCount; p++) {
@@ -130,30 +159,50 @@
             if (i != UNTRACKED_POINTER) {
                 for (int h = 0; h < historySize; h++) {
                     final long time = move.getHistoricalEventTime(h);
+                    final float x = move.getHistoricalX(p, h);
                     final float y = move.getHistoricalY(p,  h);
-                    if (detectSwipeFromTop(i, time, y)) {
-                        return true;
+                    final int swipe = detectSwipe(i, time, x, y);
+                    if (swipe != SWIPE_NONE) {
+                        return swipe;
                     }
                 }
-                if (detectSwipeFromTop(i, move.getEventTime(), move.getY(p))) {
-                    return true;
+                final int swipe = detectSwipe(i, move.getEventTime(), move.getX(p), move.getY(p));
+                if (swipe != SWIPE_NONE) {
+                    return swipe;
                 }
             }
         }
-        return false;
+        return SWIPE_NONE;
     }
 
-    private boolean detectSwipeFromTop(int i, long time, float y) {
+    private int detectSwipe(int i, long time, float x, float y) {
+        final float fromX = mDownX[i];
         final float fromY = mDownY[i];
         final long elapsed = time - mDownTime[i];
         if (DEBUG) Slog.d(TAG, "pointer " + mDownPointerId[i]
-                + " moved from.y=" + fromY + " to.y=" + y + " in " + elapsed);
-        return fromY <= mSwipeStartThreshold
+                + " moved (" + fromX + "->" + x + "," + fromY + "->" + y + ") in " + elapsed);
+        if (fromY <= mSwipeStartThreshold
                 && y > fromY + mSwipeEndThreshold
-                && elapsed < SWIPE_TIMEOUT_MS;
+                && elapsed < SWIPE_TIMEOUT_MS) {
+            return SWIPE_FROM_TOP;
+        }
+        if (fromY >= screenHeight - mSwipeStartThreshold
+                && y < fromY - mSwipeEndThreshold
+                && elapsed < SWIPE_TIMEOUT_MS) {
+            return SWIPE_FROM_BOTTOM;
+        }
+        if (fromX >= screenWidth - mSwipeStartThreshold
+                && x < fromX - mSwipeEndThreshold
+                && elapsed < SWIPE_TIMEOUT_MS) {
+            return SWIPE_FROM_RIGHT;
+        }
+        return SWIPE_NONE;
     }
 
     interface Callbacks {
         void onSwipeFromTop();
+        void onSwipeFromBottom();
+        void onSwipeFromRight();
+        void onDebug();
     }
 }