Seascape Navigation Bar View

Allow placing navigation bar on the left side aka seascape. Also
deal with fallout from this change in frame calculations, decor view,
and all over SystemUI - notably no changes to the navigation bar view.

Bug: 28823676
Change-Id: I91187a974a10a940787a858e2609f2e9c5bade78
diff --git a/core/java/com/android/internal/policy/BackdropFrameRenderer.java b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
index 28281b3c..8addffb0 100644
--- a/core/java/com/android/internal/policy/BackdropFrameRenderer.java
+++ b/core/java/com/android/internal/policy/BackdropFrameRenderer.java
@@ -370,6 +370,8 @@
                 systemInsets.bottom);
         final int rightInset = DecorView.getColorViewRightInset(stableInsets.right,
                 systemInsets.right);
+        final int leftInset = DecorView.getColorViewLeftInset(stableInsets.left,
+                systemInsets.left);
         if (mStatusBarColor != null) {
             mStatusBarColor.setBounds(0, 0, left + width, topInset);
             mStatusBarColor.draw(canvas);
@@ -379,9 +381,11 @@
         // don't want the navigation bar background be moving around when resizing in docked mode.
         // However, we need it for the transitions into/out of docked mode.
         if (mNavigationBarColor != null && fullscreen) {
-            final int size = DecorView.getNavBarSize(bottomInset, rightInset);
+            final int size = DecorView.getNavBarSize(bottomInset, rightInset, leftInset);
             if (DecorView.isNavBarToRightEdge(bottomInset, rightInset)) {
                 mNavigationBarColor.setBounds(width - size, 0, width, height);
+            } else if (DecorView.isNavBarToLeftEdge(bottomInset, rightInset)) {
+                mNavigationBarColor.setBounds(0, 0, size, height);
             } else {
                 mNavigationBarColor.setBounds(0, height - size, width, height);
             }
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 7e38d9b..1099ef7 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -97,7 +97,6 @@
 import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
-import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -162,13 +161,13 @@
 
     private final ColorViewState mStatusColorViewState = new ColorViewState(
             SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
-            Gravity.TOP, Gravity.LEFT,
+            Gravity.TOP, Gravity.LEFT, Gravity.RIGHT,
             Window.STATUS_BAR_BACKGROUND_TRANSITION_NAME,
             com.android.internal.R.id.statusBarBackground,
             FLAG_FULLSCREEN);
     private final ColorViewState mNavigationColorViewState = new ColorViewState(
             SYSTEM_UI_FLAG_HIDE_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION,
-            Gravity.BOTTOM, Gravity.RIGHT,
+            Gravity.BOTTOM, Gravity.RIGHT, Gravity.LEFT,
             Window.NAVIGATION_BAR_BACKGROUND_TRANSITION_NAME,
             com.android.internal.R.id.navigationBarBackground,
             0 /* hideWindowFlag */);
@@ -184,9 +183,11 @@
     private int mLastTopInset = 0;
     private int mLastBottomInset = 0;
     private int mLastRightInset = 0;
+    private int mLastLeftInset = 0;
     private boolean mLastHasTopStableInset = false;
     private boolean mLastHasBottomStableInset = false;
     private boolean mLastHasRightStableInset = false;
+    private boolean mLastHasLeftStableInset = false;
     private int mLastWindowFlags = 0;
     private boolean mLastShouldAlwaysConsumeNavBar = false;
 
@@ -991,12 +992,21 @@
         return Math.min(stableRight, systemRight);
     }
 
+    static int getColorViewLeftInset(int stableLeft, int systemLeft) {
+        return Math.min(stableLeft, systemLeft);
+    }
+
     static boolean isNavBarToRightEdge(int bottomInset, int rightInset) {
         return bottomInset == 0 && rightInset > 0;
     }
 
-    static int getNavBarSize(int bottomInset, int rightInset) {
-        return isNavBarToRightEdge(bottomInset, rightInset) ? rightInset : bottomInset;
+    static boolean isNavBarToLeftEdge(int bottomInset, int leftInset) {
+        return bottomInset == 0 && leftInset > 0;
+    }
+
+    static int getNavBarSize(int bottomInset, int rightInset, int leftInset) {
+        return isNavBarToRightEdge(bottomInset, rightInset) ? rightInset
+                : isNavBarToLeftEdge(bottomInset, leftInset) ? leftInset : bottomInset;
     }
 
     WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
@@ -1016,6 +1026,8 @@
                         insets.getSystemWindowInsetBottom());
                 mLastRightInset = getColorViewRightInset(insets.getStableInsetRight(),
                         insets.getSystemWindowInsetRight());
+                mLastLeftInset = getColorViewRightInset(insets.getStableInsetLeft(),
+                        insets.getSystemWindowInsetLeft());
 
                 // Don't animate if the presence of stable insets has changed, because that
                 // indicates that the window was either just added and received them for the
@@ -1031,21 +1043,32 @@
                 boolean hasRightStableInset = insets.getStableInsetRight() != 0;
                 disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset);
                 mLastHasRightStableInset = hasRightStableInset;
+
+                boolean hasLeftStableInset = insets.getStableInsetLeft() != 0;
+                disallowAnimate |= (hasLeftStableInset != mLastHasLeftStableInset);
+                mLastHasLeftStableInset = hasLeftStableInset;
+
                 mLastShouldAlwaysConsumeNavBar = insets.shouldAlwaysConsumeNavBar();
             }
 
             boolean navBarToRightEdge = isNavBarToRightEdge(mLastBottomInset, mLastRightInset);
-            int navBarSize = getNavBarSize(mLastBottomInset, mLastRightInset);
+            boolean navBarToLeftEdge = isNavBarToLeftEdge(mLastBottomInset, mLastLeftInset);
+            int navBarSize = getNavBarSize(mLastBottomInset, mLastRightInset, mLastLeftInset);
             updateColorViewInt(mNavigationColorViewState, sysUiVisibility,
-                    mWindow.mNavigationBarColor, navBarSize, navBarToRightEdge,
-                    0 /* rightInset */, animate && !disallowAnimate, false /* force */);
+                    mWindow.mNavigationBarColor, navBarSize, navBarToRightEdge || navBarToLeftEdge,
+                    navBarToLeftEdge,
+                    0 /* sideInset */, animate && !disallowAnimate, false /* force */);
 
             boolean statusBarNeedsRightInset = navBarToRightEdge
                     && mNavigationColorViewState.present;
-            int statusBarRightInset = statusBarNeedsRightInset ? mLastRightInset : 0;
+            boolean statusBarNeedsLeftInset = navBarToLeftEdge
+                    && mNavigationColorViewState.present;
+            int statusBarSideInset = statusBarNeedsRightInset ? mLastRightInset
+                    : statusBarNeedsLeftInset ? mLastLeftInset : 0;
             updateColorViewInt(mStatusColorViewState, sysUiVisibility,
                     calculateStatusBarColor(), mLastTopInset,
-                    false /* matchVertical */, statusBarRightInset, animate && !disallowAnimate,
+                    false /* matchVertical */, statusBarNeedsLeftInset, statusBarSideInset,
+                    animate && !disallowAnimate,
                     mForceWindowDrawsStatusBarBackground);
         }
 
@@ -1070,15 +1093,17 @@
         int consumedTop = consumingStatusBar ? mLastTopInset : 0;
         int consumedRight = consumingNavBar ? mLastRightInset : 0;
         int consumedBottom = consumingNavBar ? mLastBottomInset : 0;
+        int consumedLeft = consumingNavBar ? mLastLeftInset : 0;
 
         if (mContentRoot != null
                 && mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
             MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams();
             if (lp.topMargin != consumedTop || lp.rightMargin != consumedRight
-                    || lp.bottomMargin != consumedBottom) {
+                    || lp.bottomMargin != consumedBottom || lp.leftMargin != consumedLeft) {
                 lp.topMargin = consumedTop;
                 lp.rightMargin = consumedRight;
                 lp.bottomMargin = consumedBottom;
+                lp.leftMargin = consumedLeft;
                 mContentRoot.setLayoutParams(lp);
 
                 if (insets == null) {
@@ -1089,7 +1114,7 @@
             }
             if (insets != null) {
                 insets = insets.replaceSystemWindowInsets(
-                        insets.getSystemWindowInsetLeft(),
+                        insets.getSystemWindowInsetLeft() - consumedLeft,
                         insets.getSystemWindowInsetTop() - consumedTop,
                         insets.getSystemWindowInsetRight() - consumedRight,
                         insets.getSystemWindowInsetBottom() - consumedBottom);
@@ -1126,11 +1151,12 @@
      * @param size the current size in the non-parent-matching dimension.
      * @param verticalBar if true the view is attached to a vertical edge, otherwise to a
      *                    horizontal edge,
-     * @param rightMargin rightMargin for the color view.
+     * @param sideMargin sideMargin for the color view.
      * @param animate if true, the change will be animated.
      */
     private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
-            int size, boolean verticalBar, int rightMargin, boolean animate, boolean force) {
+            int size, boolean verticalBar, boolean seascape, int sideMargin,
+            boolean animate, boolean force) {
         state.present = (sysUiVis & state.systemUiHideFlag) == 0
                 && (mWindow.getAttributes().flags & state.hideWindowFlag) == 0
                 && ((mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
@@ -1145,7 +1171,9 @@
 
         int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size;
         int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT;
-        int resolvedGravity = verticalBar ? state.horizontalGravity : state.verticalGravity;
+        int resolvedGravity = verticalBar
+                ? (seascape ? state.seascapeGravity : state.horizontalGravity)
+                : state.verticalGravity;
 
         if (view == null) {
             if (showView) {
@@ -1159,7 +1187,11 @@
 
                 LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight,
                         resolvedGravity);
-                lp.rightMargin = rightMargin;
+                if (seascape) {
+                    lp.leftMargin = sideMargin;
+                } else {
+                    lp.rightMargin = sideMargin;
+                }
                 addView(view, lp);
                 updateColorViewTranslations();
             }
@@ -1168,12 +1200,16 @@
             visibilityChanged = state.targetVisibility != vis;
             state.targetVisibility = vis;
             LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            int rightMargin = seascape ? 0 : sideMargin;
+            int leftMargin = seascape ? sideMargin : 0;
             if (lp.height != resolvedHeight || lp.width != resolvedWidth
-                    || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin) {
+                    || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin
+                    || lp.leftMargin != leftMargin) {
                 lp.height = resolvedHeight;
                 lp.width = resolvedWidth;
                 lp.gravity = resolvedGravity;
                 lp.rightMargin = rightMargin;
+                lp.leftMargin = leftMargin;
                 view.setLayoutParams(lp);
             }
             if (showView) {
@@ -2210,17 +2246,19 @@
         final int translucentFlag;
         final int verticalGravity;
         final int horizontalGravity;
+        final int seascapeGravity;
         final String transitionName;
         final int hideWindowFlag;
 
         ColorViewState(int systemUiHideFlag,
                 int translucentFlag, int verticalGravity, int horizontalGravity,
-                String transitionName, int id, int hideWindowFlag) {
+                int seascapeGravity, String transitionName, int id, int hideWindowFlag) {
             this.id = id;
             this.systemUiHideFlag = systemUiHideFlag;
             this.translucentFlag = translucentFlag;
             this.verticalGravity = verticalGravity;
             this.horizontalGravity = horizontalGravity;
+            this.seascapeGravity = seascapeGravity;
             this.transitionName = transitionName;
             this.hideWindowFlag = hideWindowFlag;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
index dba7130..bfa43fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ScrimView.java
@@ -42,6 +42,7 @@
     private float mViewAlpha = 1.0f;
     private ValueAnimator mAlphaAnimator;
     private Rect mExcludedRect = new Rect();
+    private int mLeftInset = 0;
     private boolean mHasExcludedArea;
     private ValueAnimator.AnimatorUpdateListener mAlphaUpdateListener
             = new ValueAnimator.AnimatorUpdateListener() {
@@ -87,12 +88,12 @@
                 if (mExcludedRect.top > 0) {
                     canvas.drawRect(0, 0, getWidth(), mExcludedRect.top, mPaint);
                 }
-                if (mExcludedRect.left > 0) {
-                    canvas.drawRect(0,  mExcludedRect.top, mExcludedRect.left, mExcludedRect.bottom,
-                            mPaint);
+                if (mExcludedRect.left + mLeftInset > 0) {
+                    canvas.drawRect(0,  mExcludedRect.top, mExcludedRect.left + mLeftInset,
+                            mExcludedRect.bottom, mPaint);
                 }
-                if (mExcludedRect.right < getWidth()) {
-                    canvas.drawRect(mExcludedRect.right,
+                if (mExcludedRect.right + mLeftInset < getWidth()) {
+                    canvas.drawRect(mExcludedRect.right + mLeftInset,
                             mExcludedRect.top,
                             getWidth(),
                             mExcludedRect.bottom,
@@ -183,4 +184,14 @@
     public void setChangeRunnable(Runnable changeRunnable) {
         mChangeRunnable = changeRunnable;
     }
+
+    public void setLeftInset(int leftInset) {
+        if (mLeftInset != leftInset) {
+            mLeftInset = leftInset;
+
+            if (mHasExcludedArea) {
+                invalidate();
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 9c4480e..135c294 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -524,6 +524,10 @@
         mScrimBehind.setExcludedArea(area);
     }
 
+    public void setLeftInset(int inset) {
+        mScrimBehind.setLeftInset(inset);
+    }
+
     public int getScrimBehindColor() {
         return mScrimBehind.getScrimColorWithAlpha();
     }
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 ebfa018..7b22b88 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -70,6 +70,7 @@
     private View mBrightnessMirror;
 
     private int mRightInset = 0;
+    private int mLeftInset = 0;
 
     private PhoneStatusBar mService;
     private final Paint mTransparentSrcPaint = new Paint();
@@ -93,25 +94,26 @@
     @Override
     protected boolean fitSystemWindows(Rect insets) {
         if (getFitsSystemWindows()) {
-            boolean paddingChanged = insets.left != getPaddingLeft()
-                    || insets.top != getPaddingTop()
+            boolean paddingChanged = insets.top != getPaddingTop()
                     || insets.bottom != getPaddingBottom();
 
             // Super-special right inset handling, because scrims and backdrop need to ignore it.
-            if (insets.right != mRightInset) {
+            if (insets.right != mRightInset || insets.left != mLeftInset) {
                 mRightInset = insets.right;
+                mLeftInset = insets.left;
                 applyMargins();
             }
-            // Drop top inset, apply left inset and pass through bottom inset.
+            // Drop top inset, and pass through bottom inset.
             if (paddingChanged) {
-                setPadding(insets.left, 0, 0, 0);
+                setPadding(0, 0, 0, 0);
             }
             insets.left = 0;
             insets.top = 0;
             insets.right = 0;
         } else {
-            if (mRightInset != 0) {
+            if (mRightInset != 0 || mLeftInset != 0) {
                 mRightInset = 0;
+                mLeftInset = 0;
                 applyMargins();
             }
             boolean changed = getPaddingLeft() != 0
@@ -127,13 +129,16 @@
     }
 
     private void applyMargins() {
+        mService.mScrimController.setLeftInset(mLeftInset);
         final int N = getChildCount();
         for (int i = 0; i < N; i++) {
             View child = getChildAt(i);
             if (child.getLayoutParams() instanceof LayoutParams) {
                 LayoutParams lp = (LayoutParams) child.getLayoutParams();
-                if (!lp.ignoreRightInset && lp.rightMargin != mRightInset) {
+                if (!lp.ignoreRightInset
+                        && (lp.rightMargin != mRightInset || lp.leftMargin != mLeftInset)) {
                     lp.rightMargin = mRightInset;
+                    lp.leftMargin = mLeftInset;
                     child.requestLayout();
                 }
             }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 14ed190..f9eeda4 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -262,6 +262,10 @@
     private static final String SYSUI_SCREENSHOT_ERROR_RECEIVER =
             "com.android.systemui.screenshot.ScreenshotServiceErrorReceiver";
 
+    private static final int NAV_BAR_BOTTOM = 0;
+    private static final int NAV_BAR_RIGHT = 1;
+    private static final int NAV_BAR_LEFT = 2;
+
     /**
      * Keyguard stuff
      */
@@ -354,9 +358,8 @@
     int mStatusBarHeight;
     WindowState mNavigationBar = null;
     boolean mHasNavigationBar = false;
-    boolean mCanHideNavigationBar = false;
     boolean mNavigationBarCanMove = false; // can the navigation bar ever move to the side?
-    boolean mNavigationBarOnBottom = true; // is the navigation bar on the bottom *right now*?
+    int mNavigationBarPosition = NAV_BAR_BOTTOM;
     int[] mNavigationBarHeightForRotationDefault = new int[4];
     int[] mNavigationBarWidthForRotationDefault = new int[4];
     int[] mNavigationBarHeightForRotationInCarMode = new int[4];
@@ -1683,13 +1686,19 @@
                     }
                     @Override
                     public void onSwipeFromBottom() {
-                        if (mNavigationBar != null && mNavigationBarOnBottom) {
+                        if (mNavigationBar != null && mNavigationBarPosition == NAV_BAR_BOTTOM) {
                             requestTransientBars(mNavigationBar);
                         }
                     }
                     @Override
                     public void onSwipeFromRight() {
-                        if (mNavigationBar != null && !mNavigationBarOnBottom) {
+                        if (mNavigationBar != null && mNavigationBarPosition == NAV_BAR_RIGHT) {
+                            requestTransientBars(mNavigationBar);
+                        }
+                    }
+                    @Override
+                    public void onSwipeFromLeft() {
+                        if (mNavigationBar != null && mNavigationBarPosition == NAV_BAR_LEFT) {
                             requestTransientBars(mNavigationBar);
                         }
                     }
@@ -2784,8 +2793,8 @@
             if (win.getAttrs().windowAnimations != 0) {
                 return 0;
             }
-            // This can be on either the bottom or the right.
-            if (mNavigationBarOnBottom) {
+            // This can be on either the bottom or the right or the left.
+            if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
                 if (transit == TRANSIT_EXIT
                         || transit == TRANSIT_HIDE) {
                     return R.anim.dock_bottom_exit;
@@ -2793,7 +2802,7 @@
                         || transit == TRANSIT_SHOW) {
                     return R.anim.dock_bottom_enter;
                 }
-            } else {
+            } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
                 if (transit == TRANSIT_EXIT
                         || transit == TRANSIT_HIDE) {
                     return R.anim.dock_right_exit;
@@ -2801,6 +2810,14 @@
                         || transit == TRANSIT_SHOW) {
                     return R.anim.dock_right_enter;
                 }
+            } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
+                if (transit == TRANSIT_EXIT
+                        || transit == TRANSIT_HIDE) {
+                    return R.anim.dock_left_exit;
+                } else if (transit == TRANSIT_ENTER
+                        || transit == TRANSIT_SHOW) {
+                    return R.anim.dock_left_enter;
+                }
             }
         } else if (win.getAttrs().type == TYPE_DOCK_DIVIDER) {
             return selectDockedDividerAnimationLw(win, transit);
@@ -2829,10 +2846,12 @@
         // If the divider is behind the navigation bar, don't animate.
         final Rect frame = win.getFrameLw();
         final boolean behindNavBar = mNavigationBar != null
-                && ((mNavigationBarOnBottom
+                && ((mNavigationBarPosition == NAV_BAR_BOTTOM
                         && frame.top + insets >= mNavigationBar.getFrameLw().top)
-                || (!mNavigationBarOnBottom
-                        && frame.left + insets >= mNavigationBar.getFrameLw().left));
+                || (mNavigationBarPosition == NAV_BAR_RIGHT
+                        && frame.left + insets >= mNavigationBar.getFrameLw().left)
+                || (mNavigationBarPosition == NAV_BAR_LEFT
+                        && frame.right - insets <= mNavigationBar.getFrameLw().right));
         final boolean landscape = frame.height() > frame.width();
         final boolean offscreenLandscape = landscape && (frame.right - insets <= 0
                 || frame.left + insets >= win.getDisplayFrameLw().right);
@@ -4025,7 +4044,7 @@
             navVisible |= !canHideNavigationBar();
 
             boolean updateSysUiVisibility = layoutNavigationBar(displayWidth, displayHeight,
-                    displayRotation, uiMode, overscanRight, overscanBottom, dcf, navVisible, navTranslucent,
+                    displayRotation, uiMode, overscanLeft, overscanRight, overscanBottom, dcf, navVisible, navTranslucent,
                     navAllowedHidden, statusBarExpandedNotKeyguard);
             if (DEBUG_LAYOUT) Slog.i(TAG, String.format("mDock rect: (%d,%d - %d,%d)",
                     mDockLeft, mDockTop, mDockRight, mDockBottom));
@@ -4104,8 +4123,8 @@
     }
 
     private boolean layoutNavigationBar(int displayWidth, int displayHeight, int displayRotation,
-            int uiMode, int overscanRight, int overscanBottom, Rect dcf, boolean navVisible,
-            boolean navTranslucent, boolean navAllowedHidden,
+            int uiMode, int overscanLeft, int overscanRight, int overscanBottom, Rect dcf,
+            boolean navVisible, boolean navTranslucent, boolean navAllowedHidden,
             boolean statusBarExpandedNotKeyguard) {
         if (mNavigationBar != null) {
             boolean transientNavBarShowing = mNavigationBarController.isTransientShowing();
@@ -4113,8 +4132,9 @@
             // size.  We need to do this directly, instead of relying on
             // it to bubble up from the nav bar, because this needs to
             // change atomically with screen rotations.
-            mNavigationBarOnBottom = isNavigationBarOnBottom(displayWidth, displayHeight);
-            if (mNavigationBarOnBottom) {
+            mNavigationBarPosition = navigationBarPosition(displayWidth, displayHeight,
+                    displayRotation);
+            if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
                 // It's a system nav bar or a portrait screen; nav bar goes on bottom.
                 int top = displayHeight - overscanBottom
                         - getNavigationBarHeight(displayRotation, uiMode);
@@ -4140,7 +4160,7 @@
                     // we can tell the app that it is covered by it.
                     mSystemBottom = mTmpNavigationFrame.top;
                 }
-            } else {
+            } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
                 // Landscape screen; nav bar goes to the right.
                 int left = displayWidth - overscanRight
                         - getNavigationBarWidth(displayRotation, uiMode);
@@ -4166,6 +4186,33 @@
                     // we can tell the app that it is covered by it.
                     mSystemRight = mTmpNavigationFrame.left;
                 }
+            } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
+                // Seascape screen; nav bar goes to the left.
+                int right = overscanLeft + getNavigationBarWidth(displayRotation, uiMode);
+                mTmpNavigationFrame.set(overscanLeft, 0, right, displayHeight);
+                mStableLeft = mStableFullscreenLeft = mTmpNavigationFrame.right;
+                if (transientNavBarShowing) {
+                    mNavigationBarController.setBarShowingLw(true);
+                } else if (navVisible) {
+                    mNavigationBarController.setBarShowingLw(true);
+                    mDockLeft = mTmpNavigationFrame.right;
+                    // TODO: not so sure about those:
+                    mRestrictedScreenLeft = mRestrictedOverscanScreenLeft = mDockLeft;
+                    mRestrictedScreenWidth = mDockRight - mRestrictedScreenLeft;
+                    mRestrictedOverscanScreenWidth = mDockRight - mRestrictedOverscanScreenLeft;
+                } else {
+                    // We currently want to hide the navigation UI - unless we expanded the status
+                    // bar.
+                    mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
+                }
+                if (navVisible && !navTranslucent && !navAllowedHidden
+                        && !mNavigationBar.isAnimatingLw()
+                        && !mNavigationBarController.wasRecentlyTranslucent()) {
+                    // If the nav bar is currently requested to be visible,
+                    // and not in the process of animating on or off, then
+                    // we can tell the app that it is covered by it.
+                    mSystemLeft = mTmpNavigationFrame.right;
+                }
             }
             // Make sure the content and current rectangles are updated to
             // account for the restrictions from the navigation bar.
@@ -4186,8 +4233,15 @@
         return false;
     }
 
-    private boolean isNavigationBarOnBottom(int displayWidth, int displayHeight) {
-        return !mNavigationBarCanMove || displayWidth < displayHeight;
+    private int navigationBarPosition(int displayWidth, int displayHeight, int displayRotation) {
+        if (mNavigationBarCanMove && displayWidth > displayHeight) {
+            if (displayRotation == Surface.ROTATION_270) {
+                return NAV_BAR_LEFT;
+            } else {
+                return NAV_BAR_RIGHT;
+            }
+        }
+        return NAV_BAR_BOTTOM;
     }
 
     /** {@inheritDoc} */
@@ -4363,7 +4417,11 @@
             if (mStatusBar != null && mFocusedWindow == mStatusBar && canReceiveInput(mStatusBar)) {
                 // The status bar forces the navigation bar while it's visible. Make sure the IME
                 // avoids the navigation bar in that case.
-                pf.right = df.right = of.right = cf.right = vf.right = mStableRight;
+                if (mNavigationBarPosition == NAV_BAR_RIGHT) {
+                    pf.right = df.right = of.right = cf.right = vf.right = mStableRight;
+                } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
+                    pf.left = df.left = of.left = cf.left = vf.left = mStableLeft;
+                }
             }
             // IM dock windows always go to the bottom of the screen.
             attrs.gravity = Gravity.BOTTOM;
@@ -6464,10 +6522,13 @@
 
         // Only navigation bar
         if (mNavigationBar != null) {
-            if (isNavigationBarOnBottom(displayWidth, displayHeight)) {
+            int position = navigationBarPosition(displayWidth, displayHeight, displayRotation);
+            if (position == NAV_BAR_BOTTOM) {
                 outInsets.bottom = getNavigationBarHeight(displayRotation, mUiMode);
-            } else {
+            } else if (position == NAV_BAR_RIGHT) {
                 outInsets.right = getNavigationBarWidth(displayRotation, mUiMode);
+            } else if (position == NAV_BAR_LEFT) {
+                outInsets.left = getNavigationBarWidth(displayRotation, mUiMode);
             }
         }
     }
diff --git a/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java
index 80e4341..598c58e 100644
--- a/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/policy/SystemGesturesPointerEventListener.java
@@ -43,6 +43,7 @@
     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 static final int SWIPE_FROM_LEFT = 4;
 
     private final Context mContext;
     private final int mSwipeStartThreshold;
@@ -127,6 +128,9 @@
                     } else if (swipe == SWIPE_FROM_RIGHT) {
                         if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight");
                         mCallbacks.onSwipeFromRight();
+                    } else if (swipe == SWIPE_FROM_LEFT) {
+                        if (DEBUG) Slog.d(TAG, "Firing onSwipeFromLeft");
+                        mCallbacks.onSwipeFromLeft();
                     }
                 }
                 break;
@@ -229,6 +233,11 @@
                 && elapsed < SWIPE_TIMEOUT_MS) {
             return SWIPE_FROM_RIGHT;
         }
+        if (fromX <= mSwipeStartThreshold
+                && x > fromX + mSwipeDistanceThreshold
+                && elapsed < SWIPE_TIMEOUT_MS) {
+            return SWIPE_FROM_LEFT;
+        }
         return SWIPE_NONE;
     }
 
@@ -265,6 +274,7 @@
         void onSwipeFromTop();
         void onSwipeFromBottom();
         void onSwipeFromRight();
+        void onSwipeFromLeft();
         void onFling(int durationMs);
         void onDown();
         void onUpOrCancel();