Merge "Ensure thumbnail cache test suite is running on main thread."
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 86bbd075..a068095 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2512,7 +2512,6 @@
 
         TypedArray sa = res.obtainAttributes(parser,
                 com.android.internal.R.styleable.AndroidManifestPermissionGroup);
-
         if (!parsePackageItemInfo(owner, perm.info, outError,
                 "<permission-group>", sa, true /*nameRequired*/,
                 com.android.internal.R.styleable.AndroidManifestPermissionGroup_name,
@@ -3361,7 +3360,9 @@
             }
         }
 
-        int roundIconVal = sa.getResourceId(roundIconRes, 0);
+        final boolean useRoundIcon =
+                Resources.getSystem().getBoolean(com.android.internal.R.bool.config_useRoundIcon);
+        int roundIconVal = useRoundIcon ? sa.getResourceId(roundIconRes, 0) : 0;
         if (roundIconVal != 0) {
             outInfo.icon = roundIconVal;
             outInfo.nonLocalizedLabel = null;
@@ -4575,10 +4576,12 @@
             outInfo.nonLocalizedLabel = v.coerceToString();
         }
 
-        int roundIcon = sa.getResourceId(
-                com.android.internal.R.styleable.AndroidManifestIntentFilter_roundIcon, 0);
-        if (roundIcon != 0) {
-            outInfo.icon = roundIcon;
+        final boolean useRoundIcon =
+                Resources.getSystem().getBoolean(com.android.internal.R.bool.config_useRoundIcon);
+        int roundIconVal = useRoundIcon ? sa.getResourceId(
+                com.android.internal.R.styleable.AndroidManifestIntentFilter_roundIcon, 0) : 0;
+        if (roundIconVal != 0) {
+            outInfo.icon = roundIconVal;
         } else {
             outInfo.icon = sa.getResourceId(
                     com.android.internal.R.styleable.AndroidManifestIntentFilter_icon, 0);
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/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4f6915c..5a46bfc 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2492,4 +2492,7 @@
     <!-- Component that is the default launcher when demo mode is enabled. -->
     <string name="config_demoModeLauncherComponent"></string>
 
+    <!-- Flag indicating whether round icons should be parsed from the application manifest. -->
+    <bool name="config_useRoundIcon">false</bool>
+
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 1009294..1f6e8cf 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2615,4 +2615,6 @@
 
   <!-- Used internally for assistant to launch activity transitions -->
   <java-symbol type="id" name="cross_task_transition" />
+
+  <java-symbol type="bool" name="config_useRoundIcon" />
 </resources>
diff --git a/docs/html/training/testing/performance.jd b/docs/html/training/testing/performance.jd
index 8592c0f..0c0ab7f 100644
--- a/docs/html/training/testing/performance.jd
+++ b/docs/html/training/testing/performance.jd
@@ -437,15 +437,25 @@
 </p>
 
 <ul>
-  <li>Rendering Performance 101
+  <li>
+    <a class="external-link" href="https://www.youtube.com/watch?v=HXQhu6qfTVU">
+      Rendering Performance 101</a>
   </li>
-  <li>Why 60fps?
+  <li>
+    <a class="external-link" href="https://www.youtube.com/watch?v=CaMTIgxCSqU">
+      Why 60fps?</a>
   </li>
-  <li>Android UI and the GPU
+  <li>
+    <a class="external-link" href="https://www.youtube.com/watch?v=WH9AFhgwmDw">
+      Android, UI, and the GPU</a>
   </li>
-  <li>Invalidations Layouts and performance
+  <li>
+    <a class="external-link" href="https://www.youtube.com/watch?v=we6poP0kw6E">
+      Invalidations, Layouts, and Performance</a>
   </li>
-  <li>Analyzing UI Performance with Systrace
+  <li>
+    <a href="{@docRoot}studio/profile/systrace.html">
+      Analyzing UI Performance with Systrace</a>
   </li>
 </ul>
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index efb83f2..e13e982 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -54,6 +54,7 @@
 import com.android.documentsui.State.ViewMode;
 import com.android.documentsui.dirlist.AnimationView;
 import com.android.documentsui.dirlist.DirectoryFragment;
+import com.android.documentsui.dirlist.FragmentTuner;
 import com.android.documentsui.dirlist.Model;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
@@ -95,6 +96,8 @@
 
     public abstract void onDocumentPicked(DocumentInfo doc, Model model);
     public abstract void onDocumentsPicked(List<DocumentInfo> docs);
+    public abstract FragmentTuner createFragmentTuner();
+    public abstract MenuManager getMenuManager();
 
     abstract void onTaskFinished(Uri... uris);
     abstract void refreshDirectory(int anim);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 2ca13dc..830b118 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -43,10 +43,13 @@
 import android.view.Menu;
 import android.view.MenuItem;
 
+import com.android.documentsui.MenuManager.DirectoryDetails;
 import com.android.documentsui.RecentsProvider.RecentColumns;
 import com.android.documentsui.RecentsProvider.ResumeColumns;
 import com.android.documentsui.dirlist.AnimationView;
 import com.android.documentsui.dirlist.DirectoryFragment;
+import com.android.documentsui.dirlist.FragmentTuner;
+import com.android.documentsui.dirlist.FragmentTuner.DocumentsTuner;
 import com.android.documentsui.dirlist.Model;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DurableUtils;
@@ -64,6 +67,8 @@
 public class DocumentsActivity extends BaseActivity {
     private static final int CODE_FORWARD = 42;
     private static final String TAG = "DocumentsActivity";
+    private DocumentsMenuManager mMenuManager;
+    private DirectoryDetails mDetails;
 
     public DocumentsActivity() {
         super(R.layout.documents_activity, TAG);
@@ -72,6 +77,8 @@
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
+        mMenuManager = new DocumentsMenuManager(mSearchManager, getDisplayState());
+        mDetails = new DirectoryDetails(this);
 
         if (mState.action == ACTION_CREATE) {
             final String mimeType = getIntent().getType();
@@ -214,44 +221,15 @@
     @Override
     public boolean onPrepareOptionsMenu(Menu menu) {
         super.onPrepareOptionsMenu(menu);
+        mMenuManager.updateOptionMenu(menu, mDetails);
 
         final DocumentInfo cwd = getCurrentDirectory();
 
-        boolean picking = mState.action == ACTION_CREATE
-                || mState.action == ACTION_OPEN_TREE
-                || mState.action == ACTION_PICK_COPY_DESTINATION;
-
-        if (picking) {
-            // May already be hidden because the root
-            // doesn't support search.
-            mSearchManager.showMenu(false);
-        }
-
-        final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
-        final MenuItem grid = menu.findItem(R.id.menu_grid);
-        final MenuItem list = menu.findItem(R.id.menu_list);
-        final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
-
-
-        createDir.setVisible(picking);
-        createDir.setEnabled(canCreateDirectory());
-
-        // No display options in recent directories
-        boolean inRecents = cwd == null;
-        if (picking && inRecents) {
-            grid.setVisible(false);
-            list.setVisible(false);
-        }
-
-        fileSize.setVisible(fileSize.isVisible() && !picking);
-
         if (mState.action == ACTION_CREATE) {
             final FragmentManager fm = getFragmentManager();
             SaveFragment.get(fm).prepareForDirectory(cwd);
         }
 
-        Menus.disableHiddenItems(menu);
-
         return true;
     }
 
@@ -424,6 +402,18 @@
         return (DocumentsActivity) fragment.getActivity();
     }
 
+    @Override
+    public FragmentTuner createFragmentTuner() {
+        // Currently DocumentsTuner maintains a state specific to the fragment instance. Because of
+        // that, we create a new instance everytime it is needed
+        return new DocumentsTuner(this, getDisplayState());
+    }
+
+    @Override
+    public MenuManager getMenuManager() {
+        return mMenuManager;
+    }
+
     /**
      * Loads the last used path (stack) from Recents (history).
      * The path selected is based on the calling package name. So the last
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsMenuManager.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsMenuManager.java
new file mode 100644
index 0000000..e7f0e74
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsMenuManager.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2016 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.documentsui;
+
+import static com.android.documentsui.State.ACTION_CREATE;
+import static com.android.documentsui.State.ACTION_GET_CONTENT;
+import static com.android.documentsui.State.ACTION_OPEN;
+import static com.android.documentsui.State.ACTION_OPEN_TREE;
+import static com.android.documentsui.State.ACTION_PICK_COPY_DESTINATION;
+
+import android.view.Menu;
+import android.view.MenuItem;
+
+final class DocumentsMenuManager implements MenuManager {
+
+  private final State mState;
+  private final SearchViewManager mSearchManager;
+
+  public DocumentsMenuManager(SearchViewManager searchManager, State displayState) {
+       mSearchManager = searchManager;
+       mState = displayState;
+  }
+
+  @Override
+  public void updateActionMenu(Menu menu, MenuManager.SelectionDetails selection) {
+      MenuItem open = menu.findItem(R.id.menu_open);
+      MenuItem share = menu.findItem(R.id.menu_share);
+      MenuItem delete = menu.findItem(R.id.menu_delete);
+      MenuItem rename = menu.findItem(R.id.menu_rename);
+      MenuItem selectAll = menu.findItem(R.id.menu_select_all);
+
+      open.setVisible(mState.action == ACTION_GET_CONTENT
+              || mState.action == ACTION_OPEN);
+      share.setVisible(false);
+      delete.setVisible(false);
+      rename.setVisible(false);
+      selectAll.setVisible(mState.allowMultiple);
+
+      Menus.disableHiddenItems(menu);
+  }
+
+    @Override
+    public void updateOptionMenu(Menu menu, DirectoryDetails details) {
+
+        boolean picking = mState.action == ACTION_CREATE
+                || mState.action == ACTION_OPEN_TREE
+                || mState.action == ACTION_PICK_COPY_DESTINATION;
+
+        if (picking) {
+            // May already be hidden because the root
+            // doesn't support search.
+            mSearchManager.showMenu(false);
+        }
+
+        final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
+        final MenuItem fileSize = menu.findItem(R.id.menu_file_size);
+
+        createDir.setVisible(picking);
+        createDir.setEnabled(details.canCreateDirectory());
+
+        // No display options in recent directories
+        if (picking && details.isInRecents()) {
+            final MenuItem grid = menu.findItem(R.id.menu_grid);
+            final MenuItem list = menu.findItem(R.id.menu_list);
+            grid.setVisible(false);
+            list.setVisible(false);
+        }
+
+        fileSize.setVisible(fileSize.isVisible() && !picking);
+
+        Menus.disableHiddenItems(menu);
+    }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
index e5de6ba..59b9e9b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesActivity.java
@@ -36,10 +36,13 @@
 import android.view.Menu;
 import android.view.MenuItem;
 
+import com.android.documentsui.MenuManager.DirectoryDetails;
 import com.android.documentsui.OperationDialogFragment.DialogType;
 import com.android.documentsui.RecentsProvider.ResumeColumns;
 import com.android.documentsui.dirlist.AnimationView;
 import com.android.documentsui.dirlist.DirectoryFragment;
+import com.android.documentsui.dirlist.FragmentTuner;
+import com.android.documentsui.dirlist.FragmentTuner.FilesTuner;
 import com.android.documentsui.dirlist.Model;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
@@ -67,6 +70,8 @@
     // We use the time gap to figure out whether to close app or reopen the drawer.
     private long mDrawerLastFiddled;
     private DocumentClipper mClipper;
+    private FilesMenuManager mMenuManager;
+    private DirectoryDetails mDetails;
 
     public FilesActivity() {
         super(R.layout.files_activity, TAG);
@@ -77,6 +82,13 @@
         super.onCreate(icicle);
 
         mClipper = DocumentsApplication.getDocumentClipper(this);
+        mMenuManager = new FilesMenuManager(mSearchManager);
+        mDetails = new DirectoryDetails(this) {
+            @Override
+            public boolean hasItemsToPaste() {
+                return mClipper.hasItemsToPaste();
+            }
+        };
 
         RootsFragment.show(getFragmentManager(), null);
 
@@ -198,23 +210,7 @@
     @Override
     public boolean onPrepareOptionsMenu(Menu menu) {
         super.onPrepareOptionsMenu(menu);
-
-        final RootInfo root = getCurrentRoot();
-
-        final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
-        final MenuItem pasteFromCb = menu.findItem(R.id.menu_paste_from_clipboard);
-        final MenuItem settings = menu.findItem(R.id.menu_settings);
-        final MenuItem newWindow = menu.findItem(R.id.menu_new_window);
-
-        createDir.setVisible(true);
-        createDir.setEnabled(canCreateDirectory());
-        pasteFromCb.setEnabled(mClipper.hasItemsToPaste());
-        settings.setVisible(root.hasSettings());
-        newWindow.setVisible(Shared.shouldShowFancyFeatures(this));
-
-        Menus.disableHiddenItems(menu, pasteFromCb);
-        // It hides icon if searching in progress
-        mSearchManager.updateMenu();
+        mMenuManager.updateOptionMenu(menu, mDetails);
         return true;
     }
 
@@ -462,6 +458,16 @@
         finish();
     }
 
+    @Override
+    public FragmentTuner createFragmentTuner() {
+      return new FilesTuner(this, getDisplayState());
+    }
+
+    @Override
+    public MenuManager getMenuManager() {
+      return mMenuManager;
+    }
+
     /**
      * Builds a stack for the specific Uris. Multi roots are not supported, as it's impossible
      * to know which root to select. Also, the stack doesn't contain intermediate directories.
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilesMenuManager.java b/packages/DocumentsUI/src/com/android/documentsui/FilesMenuManager.java
new file mode 100644
index 0000000..f7256ff
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilesMenuManager.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2016 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.documentsui;
+
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.android.documentsui.R;
+
+final class FilesMenuManager implements MenuManager {
+
+    private final SearchViewManager mSearchManager;
+
+    public FilesMenuManager(SearchViewManager searchManager) {
+        mSearchManager = searchManager;
+    }
+
+    @Override
+    public void updateActionMenu(Menu menu, SelectionDetails selection) {
+
+        menu.findItem(R.id.menu_open).setVisible(false); // "open" is never used in Files.
+
+        // Commands accessible only via keyboard...
+        MenuItem copy = menu.findItem(R.id.menu_copy_to_clipboard);
+        MenuItem paste = menu.findItem(R.id.menu_paste_from_clipboard);
+
+        // Commands visible in the UI...
+        MenuItem rename = menu.findItem(R.id.menu_rename);
+        MenuItem moveTo = menu.findItem(R.id.menu_move_to);
+        MenuItem copyTo = menu.findItem(R.id.menu_copy_to);
+        MenuItem share = menu.findItem(R.id.menu_share);
+        MenuItem delete = menu.findItem(R.id.menu_delete);
+
+        // Commands usually on action-bar, so we always manage visibility.
+        share.setVisible(!selection.containsDirectories() && !selection.containsPartialFiles());
+        delete.setVisible(selection.canDelete());
+
+        // Commands always in overflow, so we don't bother showing/hiding...
+        copyTo.setVisible(true);
+        moveTo.setVisible(true);
+        rename.setVisible(true);
+
+        // copy is not visible, keyboard only
+        copy.setEnabled(!selection.containsPartialFiles());
+
+        copyTo.setEnabled(!selection.containsPartialFiles());
+        moveTo.setEnabled(!selection.containsPartialFiles() && selection.canDelete());
+        rename.setEnabled(!selection.containsPartialFiles() && selection.canRename());
+
+        Menus.disableHiddenItems(menu, copy, paste);
+    }
+
+    @Override
+    public void updateOptionMenu(Menu menu, DirectoryDetails details) {
+
+        final MenuItem createDir = menu.findItem(R.id.menu_create_dir);
+        final MenuItem pasteFromCb = menu.findItem(R.id.menu_paste_from_clipboard);
+        final MenuItem settings = menu.findItem(R.id.menu_settings);
+        final MenuItem newWindow = menu.findItem(R.id.menu_new_window);
+
+        createDir.setVisible(true);
+        createDir.setEnabled(details.canCreateDirectory());
+        pasteFromCb.setEnabled(details.hasItemsToPaste());
+        settings.setVisible(details.hasRootSettings());
+        newWindow.setVisible(details.shouldShowFancyFeatures());
+
+        Menus.disableHiddenItems(menu, pasteFromCb);
+
+        // It hides icon if searching in progress
+        mSearchManager.updateMenu();
+    }
+
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/MenuManager.java b/packages/DocumentsUI/src/com/android/documentsui/MenuManager.java
new file mode 100644
index 0000000..b14fab3
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/MenuManager.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 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.documentsui;
+
+import android.view.Menu;
+
+public interface MenuManager {
+
+  /** @See DirectoryFragment.SelectionModeListener#updateActionMenu */
+  void updateActionMenu(Menu mMenu, MenuManager.SelectionDetails selectionDetails);
+  /** @See Activity#onPrepareOptionsMenu */
+  void updateOptionMenu(Menu menu, DirectoryDetails details);
+
+  /**
+   * Access to meta data about the selection.
+   */
+  interface SelectionDetails {
+    boolean containsDirectories();
+    boolean containsPartialFiles();
+
+    // TODO: Update these to express characteristics instead of answering concrete questions,
+    // since the answer to those questions is (or can be) activity specific.
+    boolean canDelete();
+    boolean canRename();
+  }
+
+  public static class DirectoryDetails {
+      private final BaseActivity mActivity;
+
+      public DirectoryDetails(BaseActivity activity) {
+        mActivity = activity;
+      }
+
+      public boolean shouldShowFancyFeatures() {
+        return Shared.shouldShowFancyFeatures(mActivity);
+      }
+
+      public boolean hasRootSettings() {
+        return mActivity.getCurrentRoot().hasSettings();
+      }
+
+      public boolean hasItemsToPaste() {
+          return false;
+      }
+
+      public boolean isInRecents() {
+        return mActivity.getCurrentDirectory() == null;
+      }
+
+      public boolean canCreateDirectory() {
+        return mActivity.canCreateDirectory();
+      }
+  }
+}
\ No newline at end of file
diff --git a/packages/DocumentsUI/src/com/android/documentsui/SearchViewManager.java b/packages/DocumentsUI/src/com/android/documentsui/SearchViewManager.java
index 11b8891..46a14e6 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/SearchViewManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/SearchViewManager.java
@@ -37,7 +37,7 @@
 /**
  * Manages searching UI behavior.
  */
-final class SearchViewManager implements
+public class SearchViewManager implements
         SearchView.OnCloseListener, OnQueryTextListener, OnClickListener, OnFocusChangeListener,
         OnActionExpandListener {
 
@@ -46,7 +46,7 @@
         void onSearchFinished();
     }
 
-    public static final String TAG = "SearchManger";
+    private static final String TAG = "SearchManager";
 
     private SearchManagerListener mListener;
     private boolean mSearchExpanded;
@@ -129,7 +129,7 @@
                 && ((root.flags & Root.FLAG_SUPPORTS_SEARCH) != 0));
     }
 
-    void showMenu(boolean visible) {
+    protected void showMenu(boolean visible) {
         if (mMenuItem == null) {
             if (DEBUG) Log.d(TAG, "showMenu called before Search MenuItem installed.");
             return;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index da0b347..303db2e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -85,6 +85,7 @@
 import com.android.documentsui.Events;
 import com.android.documentsui.Events.MotionInputEvent;
 import com.android.documentsui.ItemDragListener;
+import com.android.documentsui.MenuManager;
 import com.android.documentsui.Menus;
 import com.android.documentsui.MessageBar;
 import com.android.documentsui.Metrics;
@@ -181,6 +182,7 @@
     private @Nullable ActionMode mActionMode;
 
     private DirectoryDragListener mOnDragListener;
+    private MenuManager mMenuManager;
 
     /**
      * A callback to show snackbar at the beginning of moving and copying.
@@ -320,7 +322,9 @@
         // Make sure this is done after the RecyclerView is set up.
         mFocusManager = new FocusManager(context, mRecView, mModel);
 
-        mTuner = FragmentTuner.pick(getContext(), state);
+        final BaseActivity activity = getBaseActivity();
+        mTuner = activity.createFragmentTuner();
+        mMenuManager = activity.getMenuManager();
         mClipper = DocumentsApplication.getDocumentClipper(getContext());
 
         final ActivityManager am = (ActivityManager) context.getSystemService(
@@ -504,7 +508,7 @@
      * and clearing selection when action mode is explicitly exited by the user.
      */
     private final class SelectionModeListener implements MultiSelectManager.Callback,
-            ActionMode.Callback, FragmentTuner.SelectionDetails {
+            ActionMode.Callback, MenuManager.SelectionDetails {
 
         private Selection mSelected = new Selection();
 
@@ -680,7 +684,7 @@
 
         private void updateActionMenu() {
             assert(mMenu != null);
-            mTuner.updateActionMenu(mMenu, this);
+            mMenuManager.updateActionMenu(mMenu, this);
             Menus.disableHiddenItems(mMenu);
         }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
index 7b0510b..e175331 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
@@ -49,16 +49,6 @@
         mState = state;
     }
 
-    public static FragmentTuner pick(Context context, State state) {
-        switch (state.action) {
-            case ACTION_BROWSE:
-                return new FilesTuner(context, state);
-            default:
-                return new DocumentsTuner(context, state);
-        }
-    }
-
-
     // Subtly different from isDocumentEnabled. The reason may be illuminated as follows.
     // A folder is enabled such that it may be double clicked, even in settings
     // when the folder itself cannot be selected. This may also be true of container types.
@@ -85,13 +75,12 @@
         return false;
     }
 
-    abstract void updateActionMenu(Menu menu, SelectionDetails selection);
     abstract void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch);
 
     /**
      * Provides support for Platform specific specializations of DirectoryFragment.
      */
-    private static final class DocumentsTuner extends FragmentTuner {
+    public static final class DocumentsTuner extends FragmentTuner {
 
         // We use this to keep track of whether a model has been previously loaded or not so we can
         // open the drawer on empty directories on first launch
@@ -147,25 +136,6 @@
         }
 
         @Override
-        public void updateActionMenu(Menu menu, SelectionDetails selection) {
-
-            MenuItem open = menu.findItem(R.id.menu_open);
-            MenuItem share = menu.findItem(R.id.menu_share);
-            MenuItem delete = menu.findItem(R.id.menu_delete);
-            MenuItem rename = menu.findItem(R.id.menu_rename);
-            MenuItem selectAll = menu.findItem(R.id.menu_select_all);
-
-            open.setVisible(mState.action == ACTION_GET_CONTENT
-                    || mState.action == ACTION_OPEN);
-            share.setVisible(false);
-            delete.setVisible(false);
-            rename.setVisible(false);
-            selectAll.setVisible(mState.allowMultiple);
-
-            Menus.disableHiddenItems(menu);
-        }
-
-        @Override
         void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch) {
             boolean showDrawer = false;
 
@@ -196,7 +166,7 @@
     /**
      * Provides support for Files activity specific specializations of DirectoryFragment.
      */
-    private static final class FilesTuner extends FragmentTuner {
+    public static final class FilesTuner extends FragmentTuner {
 
         // We use this to keep track of whether a model has been previously loaded or not so we can
         // open the drawer on empty directories on first launch
@@ -206,43 +176,7 @@
             super(context, state);
         }
 
-        @Override
-        public void updateActionMenu(Menu menu, SelectionDetails selection) {
 
-            menu.findItem(R.id.menu_open).setVisible(false);  // "open" is never used in Files.
-
-            // Commands accessible only via keyboard...
-            MenuItem copy = menu.findItem(R.id.menu_copy_to_clipboard);
-            MenuItem paste = menu.findItem(R.id.menu_paste_from_clipboard);
-
-            // Commands visible in the UI...
-            MenuItem rename = menu.findItem(R.id.menu_rename);
-            MenuItem moveTo = menu.findItem(R.id.menu_move_to);
-            MenuItem copyTo = menu.findItem(R.id.menu_copy_to);
-            MenuItem share = menu.findItem(R.id.menu_share);
-            MenuItem delete = menu.findItem(R.id.menu_delete);
-
-            // copy is not visible, keyboard only
-            copy.setEnabled(!selection.containsPartialFiles());
-
-            // Commands usually on action-bar, so we always manage visibility.
-            share.setVisible(!selection.containsDirectories() && !selection.containsPartialFiles());
-            delete.setVisible(selection.canDelete());
-
-            share.setEnabled(!selection.containsDirectories() && !selection.containsPartialFiles());
-            delete.setEnabled(selection.canDelete());
-
-            // Commands always in overflow, so we don't bother showing/hiding...
-            copyTo.setVisible(true);
-            moveTo.setVisible(true);
-            rename.setVisible(true);
-
-            copyTo.setEnabled(!selection.containsPartialFiles());
-            moveTo.setEnabled(!selection.containsPartialFiles() && selection.canDelete());
-            rename.setEnabled(!selection.containsPartialFiles() && selection.canRename());
-
-            Menus.disableHiddenItems(menu, copy, paste);
-        }
 
         @Override
         void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch) {
@@ -271,17 +205,4 @@
             return true;
         }
     }
-
-    /**
-     * Access to meta data about the selection.
-     */
-    interface SelectionDetails {
-        boolean containsDirectories();
-        boolean containsPartialFiles();
-
-        // TODO: Update these to express characteristics instead of answering concrete questions,
-        // since the answer to those questions is (or can be) activity specific.
-        boolean canDelete();
-        boolean canRename();
-    }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsMenuManagerTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsMenuManagerTest.java
new file mode 100644
index 0000000..e0c99d8
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsMenuManagerTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2016 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.documentsui;
+
+import static com.android.documentsui.State.ACTION_CREATE;
+import static com.android.documentsui.State.ACTION_OPEN;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.documentsui.testing.TestDirectoryDetails;
+import com.android.documentsui.testing.TestMenu;
+import com.android.documentsui.testing.TestMenuItem;
+import com.android.documentsui.testing.TestSearchViewManager;
+import com.android.documentsui.testing.TestSelectionDetails;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class DocumentsMenuManagerTest {
+
+    private TestMenu testMenu;
+    private TestMenuItem open;
+    private TestMenuItem share;
+    private TestMenuItem delete;
+    private TestMenuItem rename;
+    private TestMenuItem selectAll;
+    private TestMenuItem createDir;
+    private TestMenuItem fileSize;
+    private TestMenuItem grid;
+    private TestMenuItem list;
+
+    private TestSelectionDetails selectionDetails;
+    private TestDirectoryDetails directoryDetails;
+    private TestSearchViewManager testSearchManager;
+    private State state = new State();
+
+    @Before
+    public void setUp() {
+        testMenu = TestMenu.create();
+        open = testMenu.findItem(R.id.menu_open);
+        share = testMenu.findItem(R.id.menu_share);
+        delete = testMenu.findItem(R.id.menu_delete);
+        rename =  testMenu.findItem(R.id.menu_rename);
+        selectAll = testMenu.findItem(R.id.menu_select_all);
+        createDir = testMenu.findItem(R.id.menu_create_dir);
+        fileSize = testMenu.findItem(R.id.menu_file_size);
+        grid = testMenu.findItem(R.id.menu_grid);
+        list = testMenu.findItem(R.id.menu_list);
+
+        selectionDetails = new TestSelectionDetails();
+        directoryDetails = new TestDirectoryDetails();
+        testSearchManager = new TestSearchViewManager();
+        state.action = ACTION_CREATE;
+        state.allowMultiple = true;
+    }
+
+    @Test
+    public void testActionMenu() {
+        DocumentsMenuManager mgr = new DocumentsMenuManager(testSearchManager, state);
+        mgr.updateActionMenu(testMenu, selectionDetails);
+
+        open.assertInvisible();
+        delete.assertInvisible();
+        share.assertInvisible();
+        rename.assertInvisible();
+        selectAll.assertVisible();
+    }
+
+    @Test
+    public void testActionMenu_openAction() {
+        state.action = ACTION_OPEN;
+        DocumentsMenuManager mgr = new DocumentsMenuManager(testSearchManager, state);
+        mgr.updateActionMenu(testMenu, selectionDetails);
+
+        open.assertVisible();
+    }
+
+
+    @Test
+    public void testActionMenu_notAllowMultiple() {
+        state.allowMultiple = false;
+        DocumentsMenuManager mgr = new DocumentsMenuManager(testSearchManager, state);
+        mgr.updateActionMenu(testMenu, selectionDetails);
+
+        selectAll.assertInvisible();
+    }
+
+    @Test
+    public void testOptionMenu() {
+        DocumentsMenuManager mgr = new DocumentsMenuManager(testSearchManager, state);
+        mgr.updateOptionMenu(testMenu, directoryDetails);
+
+        createDir.assertDisabled();
+        fileSize.assertInvisible();
+        assertTrue(testSearchManager.showMenuCalled());
+    }
+
+    @Test
+    public void testOptionMenu_notPicking() {
+        state.action = ACTION_OPEN;
+        DocumentsMenuManager mgr = new DocumentsMenuManager(testSearchManager, state);
+        mgr.updateOptionMenu(testMenu, directoryDetails);
+
+        createDir.assertInvisible();
+        grid.assertInvisible();
+        list.assertInvisible();
+        assertFalse(testSearchManager.showMenuCalled());
+    }
+
+    @Test
+    public void testOptionMenu_canCreateDirectory() {
+        directoryDetails.canCreateDirectory = true;
+        DocumentsMenuManager mgr = new DocumentsMenuManager(testSearchManager, state);
+        mgr.updateOptionMenu(testMenu, directoryDetails);
+
+        createDir.assertEnabled();
+    }
+
+    @Test
+    public void testOptionMenu_inRecents() {
+        directoryDetails.isInRecents = true;
+        DocumentsMenuManager mgr = new DocumentsMenuManager(testSearchManager, state);
+        mgr.updateOptionMenu(testMenu, directoryDetails);
+
+        grid.assertInvisible();
+        list.assertInvisible();
+    }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesMenuManagerTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesMenuManagerTest.java
new file mode 100644
index 0000000..244f7ec
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesMenuManagerTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2016 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.documentsui;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.documentsui.testing.TestDirectoryDetails;
+import com.android.documentsui.testing.TestMenu;
+import com.android.documentsui.testing.TestMenuItem;
+import com.android.documentsui.testing.TestSearchViewManager;
+import com.android.documentsui.testing.TestSelectionDetails;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public final class FilesMenuManagerTest {
+
+    private TestMenu testMenu;
+    private TestMenuItem rename;
+    private TestMenuItem moveTo;
+    private TestMenuItem copyTo;
+    private TestMenuItem share;
+    private TestMenuItem delete;
+    private TestMenuItem createDir;
+    private TestMenuItem pasteFromCb;
+    private TestMenuItem settings;
+    private TestMenuItem newWindow;
+    private TestSelectionDetails selectionDetails;
+    private TestDirectoryDetails directoryDetails;
+    private TestSearchViewManager testSearchManager;
+
+    @Before
+    public void setUp() {
+        testMenu = TestMenu.create();
+        rename = testMenu.findItem(R.id.menu_rename);
+        moveTo = testMenu.findItem(R.id.menu_move_to);
+        copyTo = testMenu.findItem(R.id.menu_copy_to);
+        share = testMenu.findItem(R.id.menu_share);
+        delete = testMenu.findItem(R.id.menu_delete);
+        createDir = testMenu.findItem(R.id.menu_create_dir);
+        pasteFromCb = testMenu.findItem(R.id.menu_paste_from_clipboard);
+        settings = testMenu.findItem(R.id.menu_settings);
+        newWindow = testMenu.findItem(R.id.menu_new_window);
+
+        selectionDetails = new TestSelectionDetails();
+        directoryDetails = new TestDirectoryDetails();
+        testSearchManager = new TestSearchViewManager();
+    }
+
+    @Test
+    public void testActionMenu() {
+        selectionDetails.canDelete = true;
+        selectionDetails.canRename = true;
+
+        FilesMenuManager mgr = new FilesMenuManager(testSearchManager);
+        mgr.updateActionMenu(testMenu, selectionDetails);
+
+        rename.assertEnabled();
+        delete.assertVisible();
+        share.assertVisible();
+        copyTo.assertEnabled();
+        moveTo.assertEnabled();
+    }
+
+    @Test
+    public void testActionMenu_containsPartial() {
+        selectionDetails.containPartial = true;
+        FilesMenuManager mgr = new FilesMenuManager(testSearchManager);
+        mgr.updateActionMenu(testMenu, selectionDetails);
+
+        rename.assertDisabled();
+        share.assertInvisible();
+        copyTo.assertDisabled();
+        moveTo.assertDisabled();
+    }
+
+    @Test
+    public void testActionMenu_cantRename() {
+        selectionDetails.canRename = false;
+        FilesMenuManager mgr = new FilesMenuManager(testSearchManager);
+        mgr.updateActionMenu(testMenu, selectionDetails);
+
+        rename.assertDisabled();
+    }
+
+    @Test
+    public void testActionMenu_cantDelete() {
+        selectionDetails.canDelete = false;
+        FilesMenuManager mgr = new FilesMenuManager(testSearchManager);
+        mgr.updateActionMenu(testMenu, selectionDetails);
+
+        delete.assertInvisible();
+        // We shouldn't be able to move files if we can't delete them
+        moveTo.assertDisabled();
+    }
+
+    @Test
+    public void testActionMenu_containsDirectory() {
+        selectionDetails.containDirectories = true;
+        FilesMenuManager mgr = new FilesMenuManager(testSearchManager);
+        mgr.updateActionMenu(testMenu, selectionDetails);
+
+        // We can't share directories
+        share.assertInvisible();
+    }
+
+    @Test
+    public void testOptionMenu_canCreateDirectory() {
+        directoryDetails.canCreateDirectory = true;
+        FilesMenuManager mgr = new FilesMenuManager(testSearchManager);
+        mgr.updateOptionMenu(testMenu, directoryDetails);
+
+        createDir.assertEnabled();
+    }
+
+    @Test
+    public void testOptionMenu_hasItemsToPaste() {
+        directoryDetails.hasItemsToPaste = true;
+        FilesMenuManager mgr = new FilesMenuManager(testSearchManager);
+        mgr.updateOptionMenu(testMenu, directoryDetails);
+
+        pasteFromCb.assertEnabled();
+    }
+
+    @Test
+    public void testOptionMenu_hasRootSettings() {
+        directoryDetails.hasRootSettings = true;
+        FilesMenuManager mgr = new FilesMenuManager(testSearchManager);
+        mgr.updateOptionMenu(testMenu, directoryDetails);
+
+        settings.assertVisible();
+    }
+
+    @Test
+    public void testOptionMenu_shouldShowFancyFeatures() {
+        directoryDetails.shouldShowFancyFeatures = true;
+        FilesMenuManager mgr = new FilesMenuManager(testSearchManager);
+        mgr.updateOptionMenu(testMenu, directoryDetails);
+
+        newWindow.assertVisible();
+    }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestDirectoryDetails.java b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestDirectoryDetails.java
new file mode 100644
index 0000000..ce2ff36
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestDirectoryDetails.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2016 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.documentsui.testing;
+
+import com.android.documentsui.MenuManager.DirectoryDetails;
+
+/**
+ * Test copy of DirectoryDetails, everything default to false
+ */
+public class TestDirectoryDetails extends DirectoryDetails {
+
+    public boolean shouldShowFancyFeatures;
+    public boolean isInRecents;
+    public boolean hasRootSettings;
+    public boolean hasItemsToPaste;
+    public boolean canCreateDirectory;
+
+    public TestDirectoryDetails() {
+        super(null);
+    }
+
+    @Override
+    public boolean shouldShowFancyFeatures() {
+        return shouldShowFancyFeatures;
+    }
+
+    @Override
+    public boolean hasRootSettings() {
+        return hasRootSettings;
+    }
+
+    @Override
+    public boolean hasItemsToPaste() {
+        return hasItemsToPaste;
+    }
+
+    @Override
+    public boolean isInRecents() {
+        return isInRecents;
+    }
+
+    @Override
+    public boolean canCreateDirectory() {
+        return canCreateDirectory;
+    }
+}
\ No newline at end of file
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestMenu.java b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestMenu.java
new file mode 100644
index 0000000..78f26df
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestMenu.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2016 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.documentsui.testing;
+
+import android.util.SparseArray;
+import android.view.Menu;
+
+import com.android.documentsui.R;
+
+import org.mockito.Mockito;
+
+/**
+ *
+ * Test copy of {@link android.view.Menu}.
+ *
+ * We use abstract so we don't have to implement all the necessary methods from the interface,
+ * and we use Mockito to just mock out the methods we need.
+ * To get an instance, use {@link #create(int...)}.
+ */
+public abstract class TestMenu implements Menu {
+
+    private SparseArray<TestMenuItem> items = new SparseArray<>();
+
+    public static TestMenu create() {
+        return create(R.id.menu_open,
+                R.id.menu_rename,
+                R.id.menu_copy_to_clipboard,
+                R.id.menu_paste_from_clipboard,
+                R.id.menu_move_to,
+                R.id.menu_copy_to,
+                R.id.menu_share,
+                R.id.menu_delete,
+                R.id.menu_create_dir,
+                R.id.menu_paste_from_clipboard,
+                R.id.menu_settings,
+                R.id.menu_new_window,
+                R.id.menu_select_all,
+                R.id.menu_file_size,
+                R.id.menu_grid,
+                R.id.menu_list);
+    }
+
+    public static TestMenu create(int... ids) {
+        final TestMenu menu = Mockito.mock(TestMenu.class,
+                Mockito.withSettings().defaultAnswer(Mockito.CALLS_REAL_METHODS));
+        menu.items = new SparseArray<>();
+        for (int id : ids) {
+            TestMenuItem item = TestMenuItem.create(id);
+             menu.addMenuItem(id, item);
+        }
+        return menu;
+    }
+
+    public void addMenuItem(int id, TestMenuItem item) {
+        items.put(id, item);
+    }
+
+    @Override
+    public TestMenuItem findItem(int id) {
+        return items.get(id);
+    }
+
+    @Override
+    public int size() {
+        return items.size();
+    }
+
+    @Override
+    public TestMenuItem getItem(int index) {
+        return items.valueAt(index);
+    }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestMenuItem.java b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestMenuItem.java
new file mode 100644
index 0000000..406f808
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestMenuItem.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 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.documentsui.testing;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.view.MenuItem;
+
+import org.mockito.Mockito;
+
+/**
+*
+* Test copy of {@link android.view.MenuItem}.
+*
+* We use abstract so we don't have to implement all the necessary methods from the interface,
+* and we use Mockito to just mock out the methods we need.
+* To get an instance, use {@link #create(int)}.
+*/
+
+public abstract class TestMenuItem implements MenuItem {
+
+    boolean enabled;
+    boolean visible;
+
+    public static TestMenuItem create(int id) {
+        final TestMenuItem mockMenuItem = Mockito.mock(TestMenuItem.class,
+                Mockito.withSettings().defaultAnswer(Mockito.CALLS_REAL_METHODS));
+
+        return mockMenuItem;
+    }
+
+    @Override
+    public MenuItem setEnabled(boolean enabled) {
+        this.enabled = enabled;
+        return this;
+    }
+
+    @Override
+    public MenuItem setVisible(boolean visible) {
+        this.visible = visible;
+        return this;
+    }
+
+    @Override
+    public boolean isVisible() {
+        return this.visible;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return this.enabled;
+    }
+
+    public void assertEnabled() {
+        assertTrue(this.enabled);
+    }
+
+    public void assertDisabled() {
+        assertFalse(this.enabled);
+    }
+
+    public void assertVisible() {
+        assertTrue(this.visible);
+    }
+
+    public void assertInvisible() {
+        assertFalse(this.visible);
+    }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestSearchViewManager.java b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestSearchViewManager.java
new file mode 100644
index 0000000..29ae3bd
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestSearchViewManager.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 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.documentsui.testing;
+
+import android.os.Bundle;
+
+import com.android.documentsui.SearchViewManager;
+
+/**
+ * Test copy of {@link com.android.documentsui.SearchViewManager}
+ *
+ * Specficially used to test whether {@link #showMenu(boolean)}
+ * and {@link #updateMenu()} are called.
+ */
+public class TestSearchViewManager extends SearchViewManager {
+
+    boolean updateMenuCalled;
+    boolean showMenuCalled;
+
+    public TestSearchViewManager(SearchManagerListener listener, Bundle savedState) {
+        super(listener, savedState);
+    }
+
+    public TestSearchViewManager() {
+        super(null, null);
+    }
+
+    @Override
+    protected void showMenu(boolean visible) {
+        showMenuCalled = true;
+    }
+
+    @Override
+    public void updateMenu() {
+        updateMenuCalled = true;
+    }
+
+    public boolean showMenuCalled() {
+        return showMenuCalled;
+    }
+
+    public boolean updateMenuCalled() {
+        return updateMenuCalled;
+    }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestSelectionDetails.java b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestSelectionDetails.java
new file mode 100644
index 0000000..20796ca
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/testing/TestSelectionDetails.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 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.documentsui.testing;
+
+import com.android.documentsui.MenuManager.SelectionDetails;
+
+/**
+ * Test copy of SelectionDetails, everything default to false
+ */
+public class TestSelectionDetails implements SelectionDetails {
+
+    public boolean canRename;
+    public boolean canDelete;
+    public boolean containPartial;
+    public boolean containDirectories;
+
+     @Override
+     public boolean containsPartialFiles() {
+         return containPartial;
+     }
+
+     @Override
+     public boolean containsDirectories() {
+         return containDirectories;
+     }
+
+     @Override
+     public boolean canRename() {
+         return canRename;
+     }
+
+     @Override
+     public boolean canDelete() {
+         return canDelete;
+     }
+ }
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_detail_items.xml b/packages/SystemUI/res/layout/qs_detail_items.xml
index 5ca4857..0b08a55 100644
--- a/packages/SystemUI/res/layout/qs_detail_items.xml
+++ b/packages/SystemUI/res/layout/qs_detail_items.xml
@@ -20,7 +20,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:paddingTop="@dimen/qs_detail_items_padding_top"
-    android:paddingStart="16dp"
+    android:paddingStart="@dimen/qs_detail_padding_start"
     android:paddingEnd="16dp">
 
     <com.android.systemui.qs.AutoSizingList
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index cb3f080..ef3d550 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -215,6 +215,7 @@
     <dimen name="qs_data_usage_text_size">14sp</dimen>
     <dimen name="qs_data_usage_usage_text_size">36sp</dimen>
     <dimen name="qs_battery_padding">2dp</dimen>
+    <dimen name="qs_detail_padding_start">16dp</dimen>
     <dimen name="qs_detail_items_padding_top">4dp</dimen>
     <dimen name="qs_detail_item_icon_size">24dp</dimen>
     <dimen name="qs_detail_item_icon_marginStart">0dp</dimen>
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 061586d..94ede0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -518,6 +518,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 15ce017..762e170 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);
                         }
                     }
@@ -2778,8 +2787,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;
@@ -2787,7 +2796,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;
@@ -2795,6 +2804,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);
@@ -2823,10 +2840,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);
@@ -4019,7 +4038,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));
@@ -4098,8 +4117,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();
@@ -4107,8 +4126,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);
@@ -4134,7 +4154,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);
@@ -4160,6 +4180,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.
@@ -4180,8 +4227,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} */
@@ -4357,7 +4411,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();