[multi-part] Make context menu match spec.

* Update context menu items in DirectoryFragment as defined in PRD
* Fix selection by right click in pickers
* Fix several bugs caused by selection in pickers

Functionalities are not done. OpenWith and OpenInNewWindow is not
yet supported.

Bug: 31495650
Bug: 31345822
Change-Id: I7be3169e2ee69971c0e86aa8b5e1078b15eb2960
diff --git a/res/menu/container_context_menu.xml b/res/menu/container_context_menu.xml
new file mode 100644
index 0000000..08feae6
--- /dev/null
+++ b/res/menu/container_context_menu.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+  -->
+
+<!-- Context menu used when right clicks on empty area of recycler view or empty view. -->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <group
+        android:id="@+id/menu_clipboard_group">
+        <item
+            android:id="@+id/menu_select_all"
+            android:title="@string/menu_select_all" />
+
+        <item
+            android:id="@+id/menu_paste_from_clipboard"
+            android:title="@string/menu_paste_from_clipboard" />
+    </group>
+
+    <group
+        android:id="@+id/menu_modifier_group">
+        <item
+            android:id="@+id/menu_create_dir"
+            android:title="@string/menu_create_dir" />
+    </group>
+</menu>
\ No newline at end of file
diff --git a/res/menu/context_menu.xml b/res/menu/context_menu.xml
deleted file mode 100644
index abac167..0000000
--- a/res/menu/context_menu.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-    <item
-        android:id="@+id/menu_cut_to_clipboard"
-        android:title="@string/menu_cut_to_clipboard" />
-    <item
-        android:id="@+id/menu_copy_to_clipboard"
-        android:title="@string/menu_copy_to_clipboard" />
-    <item
-        android:id="@+id/menu_paste_from_clipboard"
-        android:title="@string/menu_paste_from_clipboard" />
-
-    <item
-        android:id="@+id/menu_paste_into_folder"
-        android:title="@string/menu_paste_into_folder" />
-</menu>
diff --git a/res/menu/dir_context_menu.xml b/res/menu/dir_context_menu.xml
new file mode 100644
index 0000000..8c0772d
--- /dev/null
+++ b/res/menu/dir_context_menu.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+  -->
+
+<!-- Context menu used when user right clicks on a folder with a selection that doesn't have files.
+    Selection may be empty. -->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <group
+        android:id="@+id/menu_open_group">
+        <item
+            android:id="@+id/menu_open_in_new_window"
+            android:title="@string/menu_open_in_new_window" />
+    </group>
+
+    <group
+        android:id="@+id/menu_clipboard_group">
+        <item
+            android:id="@+id/menu_cut_to_clipboard"
+            android:title="@string/menu_cut_to_clipboard" />
+        <item
+            android:id="@+id/menu_copy_to_clipboard"
+            android:title="@string/menu_copy_to_clipboard" />
+        <item
+            android:id="@+id/menu_paste_into_folder"
+            android:title="@string/menu_paste_into_folder" />
+    </group>
+
+    <group
+        android:id="@+id/menu_modifier_group">
+        <item
+            android:id="@+id/menu_rename"
+            android:title="@string/menu_rename" />
+        <item
+            android:id="@+id/menu_delete"
+            android:title="@string/menu_delete" />
+    </group>
+</menu>
\ No newline at end of file
diff --git a/res/menu/file_context_menu.xml b/res/menu/file_context_menu.xml
new file mode 100644
index 0000000..16d36bc
--- /dev/null
+++ b/res/menu/file_context_menu.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<!-- Context menu used when user right clicks on a file with a selection that doesn't have folders.
+    The selection may be empty. -->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <group
+        android:id="@+id/menu_open_group">
+        <item
+            android:id="@+id/menu_share"
+            android:title="@string/menu_share" />
+        <item
+            android:id="@+id/menu_open"
+            android:title="@string/menu_open" />
+        <item
+            android:id="@+id/menu_open_with"
+            android:title="@string/menu_open_with" />
+    </group>
+
+    <group
+        android:id="@+id/menu_clipboard_group">
+        <item
+            android:id="@+id/menu_cut_to_clipboard"
+            android:title="@string/menu_cut_to_clipboard" />
+        <item
+            android:id="@+id/menu_copy_to_clipboard"
+            android:title="@string/menu_copy_to_clipboard" />
+    </group>
+
+    <group
+        android:id="@+id/menu_modifier_group">
+        <item
+            android:id="@+id/menu_rename"
+            android:title="@string/menu_rename" />
+        <item
+            android:id="@+id/menu_delete"
+            android:title="@string/menu_delete" />
+    </group>
+</menu>
diff --git a/res/menu/mixed_context_menu.xml b/res/menu/mixed_context_menu.xml
new file mode 100644
index 0000000..9f17b22
--- /dev/null
+++ b/res/menu/mixed_context_menu.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    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.
+  -->
+
+<!-- Context menu used when user right clicks with a selection mixed with both folders and docs. -->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <group
+        android:id="@+id/menu_clipboard_group">
+        <item
+            android:id="@+id/menu_cut_to_clipboard"
+            android:title="@string/menu_cut_to_clipboard" />
+        <item
+            android:id="@+id/menu_copy_to_clipboard"
+            android:title="@string/menu_copy_to_clipboard" />
+    </group>
+
+    <group
+        android:id="@+id/menu_modifier_group">
+        <item
+            android:id="@+id/menu_delete"
+            android:title="@string/menu_delete" />
+    </group>
+</menu>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 52ad855..6032316 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -32,8 +32,6 @@
     <string name="menu_grid">Grid view</string>
     <!-- Menu item that switches view to show documents as a list [CHAR LIMIT=24] -->
     <string name="menu_list">List view</string>
-    <!-- Menu item that switches the criteria with which documents are sorted [CHAR LIMIT=24] -->
-    <string name="menu_sort">Sort by</string>
     <!-- Menu item that enters a mode to search for documents [CHAR LIMIT=24] -->
     <string name="menu_search">Search</string>
     <!-- Menu item that opens settings/options for a device (like an SD card). [CHAR LIMIT=24] -->
@@ -41,6 +39,10 @@
 
     <!-- Menu item title that opens the selected documents [CHAR LIMIT=24] -->
     <string name="menu_open">Open</string>
+    <!-- Menu item title that shows a chooser to user to pick the app to open the selected documents. [CHAR LIMIT=24] -->
+    <string name="menu_open_with">Open with</string>
+    <!-- Menu item title that opens a doc in new window. [CHAR LIMIT=24] -->
+    <string name="menu_open_in_new_window">Open in new window</string>
     <!-- Menu item title that saves the current document [CHAR LIMIT=24] -->
     <string name="menu_save">Save</string>
     <!-- Menu item title that shares the selected documents [CHAR LIMIT=24] -->
@@ -53,6 +55,8 @@
     <string name="menu_copy">Copy to\u2026</string>
     <!-- Menu item title that moves the selected documents [CHAR LIMIT=24] -->
     <string name="menu_move">Move to\u2026</string>
+    <!-- Menu item that renames the selected document [CHAR LIMIT=24] -->
+    <string name="menu_rename">Rename</string>
 
     <!-- Menu item title that creates a new window in the activity [CHAR LIMIT=24] -->
     <string name="menu_new_window">New window</string>
@@ -203,8 +207,6 @@
     </plurals>
     <!-- Toast shown when a user tries to paste files into an unsupported location. -->
     <string name="clipboard_files_cannot_paste">Cannot paste the selected files in this location.</string>
-    <!-- Menu item that renames the selected document [CHAR LIMIT=24] -->
-    <string name="menu_rename">Rename</string>
     <!-- Toast shown when renaming document failed with an error [CHAR LIMIT=48] -->
     <string name="rename_error">Failed to rename document</string>
     <!-- Context Menu item that ejects the root selected [CHAR LIMIT=24] -->
diff --git a/src/com/android/documentsui/DocumentsMenuManager.java b/src/com/android/documentsui/DocumentsMenuManager.java
index 035268e..6e19178 100644
--- a/src/com/android/documentsui/DocumentsMenuManager.java
+++ b/src/com/android/documentsui/DocumentsMenuManager.java
@@ -59,7 +59,7 @@
     }
 
     @Override
-    void updateSelectAll(MenuItem selectAll, SelectionDetails selectionDetails) {
+    void updateSelectAll(MenuItem selectAll) {
         selectAll.setVisible(mState.allowMultiple);
     }
 
@@ -70,8 +70,18 @@
     }
 
     @Override
-    void updateOpen(MenuItem open, SelectionDetails selectionDetails) {
+    void updateOpenInActionMode(MenuItem open, SelectionDetails selectionDetails) {
+        updateOpen(open, selectionDetails);
+    }
+
+    @Override
+    void updateOpenInContextMenu(MenuItem open, SelectionDetails selectionDetails) {
+        updateOpen(open, selectionDetails);
+    }
+
+    private void updateOpen(MenuItem open, SelectionDetails selectionDetails) {
         open.setVisible(mState.action == ACTION_GET_CONTENT
                 || mState.action == ACTION_OPEN);
+        open.setEnabled(selectionDetails.size() > 0);
     }
 }
diff --git a/src/com/android/documentsui/FilesMenuManager.java b/src/com/android/documentsui/FilesMenuManager.java
index cff1f0a..973c730 100644
--- a/src/com/android/documentsui/FilesMenuManager.java
+++ b/src/com/android/documentsui/FilesMenuManager.java
@@ -58,6 +58,24 @@
     }
 
     @Override
+    void updateOpenInContextMenu(MenuItem open, SelectionDetails selectionDetails) {
+        open.setEnabled(selectionDetails.size() == 1
+                && !selectionDetails.containsPartialFiles());
+    }
+
+    @Override
+    void updateOpenWith(MenuItem openWith, SelectionDetails selectionDetails) {
+        openWith.setEnabled(selectionDetails.size() == 1
+                && !selectionDetails.containsPartialFiles());
+    }
+
+    @Override
+    void updateOpenInNewWindow(MenuItem openInNewWindow, SelectionDetails selectionDetails) {
+        openInNewWindow.setEnabled(selectionDetails.size() == 1
+            && !selectionDetails.containsPartialFiles());
+    }
+
+    @Override
     void updateMoveTo(MenuItem moveTo, SelectionDetails selectionDetails) {
         moveTo.setVisible(true);
         moveTo.setEnabled(!selectionDetails.containsPartialFiles() && selectionDetails.canDelete());
@@ -75,7 +93,7 @@
     }
 
     @Override
-    void updateSelectAll(MenuItem selectAll, SelectionDetails selectionDetails) {
+    void updateSelectAll(MenuItem selectAll) {
         selectAll.setVisible(true);
     }
 
diff --git a/src/com/android/documentsui/MenuManager.java b/src/com/android/documentsui/MenuManager.java
index 974a5bf..e9daa9f 100644
--- a/src/com/android/documentsui/MenuManager.java
+++ b/src/com/android/documentsui/MenuManager.java
@@ -20,6 +20,7 @@
 import android.view.MenuItem;
 
 import com.android.documentsui.base.RootInfo;
+import com.android.documentsui.dirlist.DirectoryFragment;
 
 public abstract class MenuManager {
 
@@ -31,20 +32,20 @@
         mState = displayState;
     }
 
-    /** @See DirectoryFragment.SelectionModeListener#updateActionMenu */
+    /** @see ActionModeController */
     public void updateActionMenu(Menu menu, SelectionDetails selection) {
-        updateOpen(menu.findItem(R.id.menu_open), selection);
+        updateOpenInActionMode(menu.findItem(R.id.menu_open), selection);
         updateDelete(menu.findItem(R.id.menu_delete), selection);
         updateShare(menu.findItem(R.id.menu_share), selection);
         updateRename(menu.findItem(R.id.menu_rename), selection);
-        updateSelectAll(menu.findItem(R.id.menu_select_all), selection);
+        updateSelectAll(menu.findItem(R.id.menu_select_all));
         updateMoveTo(menu.findItem(R.id.menu_move_to), selection);
         updateCopyTo(menu.findItem(R.id.menu_copy_to), selection);
 
         Menus.disableHiddenItems(menu);
     }
 
-    /** @See Activity#onPrepareOptionsMenu */
+    /** @see BaseActivity#onPrepareOptionsMenu */
     public void updateOptionMenu(Menu menu, DirectoryDetails directoryDetails) {
         updateCreateDir(menu.findItem(R.id.menu_create_dir), directoryDetails);
         updateSettings(menu.findItem(R.id.menu_settings), directoryDetails);
@@ -57,76 +58,92 @@
         Menus.disableHiddenItems(menu);
     }
 
-    /** @See DirectoryFragment.onCreateContextMenu
+    /**
+     * @see DirectoryFragment#onCreateContextMenu
      *
-     * Called when user tries to generate a context menu anchored to a file.
-     * */
-    public void updateContextMenuForFile(
-            Menu menu,
-            SelectionDetails selectionDetails,
-            DirectoryDetails directoryDetails) {
+     * Called when user tries to generate a context menu anchored to a file when the selection
+     * doesn't contain any folder.
+     *
+     * @param selectionDetails
+     *      containsFiles may return false because this may be called when user right clicks on an
+     *      unselectable item in pickers
+     */
+    public void updateContextMenuForFiles(Menu menu, SelectionDetails selectionDetails) {
+        assert(selectionDetails != null);
+
+        MenuItem share = menu.findItem(R.id.menu_share);
+        MenuItem open = menu.findItem(R.id.menu_open);
+        MenuItem openWith = menu.findItem(R.id.menu_open_with);
+        MenuItem rename = menu.findItem(R.id.menu_rename);
+
+        updateShare(share, selectionDetails);
+        updateOpenInContextMenu(open, selectionDetails);
+        updateOpenWith(openWith, selectionDetails);
+        updateRename(rename, selectionDetails);
+
+        updateContextMenu(menu, selectionDetails);
+    }
+
+    /**
+     * @see DirectoryFragment#onCreateContextMenu
+     *
+     * Called when user tries to generate a context menu anchored to a folder when the selection
+     * doesn't contain any file.
+     *
+     * @param selectionDetails
+     *      containDirectories may return false because this may be called when user right clicks on
+     *      an unselectable item in pickers
+     */
+    public void updateContextMenuForDirs(Menu menu, SelectionDetails selectionDetails) {
+        assert(selectionDetails != null);
+
+        MenuItem openInNewWindow = menu.findItem(R.id.menu_open_in_new_window);
+        MenuItem rename = menu.findItem(R.id.menu_rename);
+        MenuItem pasteInto = menu.findItem(R.id.menu_paste_into_folder);
+
+        updateOpenInNewWindow(openInNewWindow, selectionDetails);
+        updateRename(rename, selectionDetails);
+        updatePasteInto(pasteInto, selectionDetails);
+
+        updateContextMenu(menu, selectionDetails);
+    }
+
+    /**
+     * @see DirectoryFragment#onCreateContextMenu
+     *
+     * Update shared context menu items of both files and folders context menus.
+     */
+    public void updateContextMenu(Menu menu, SelectionDetails selectionDetails) {
         assert(selectionDetails != null);
 
         MenuItem cut = menu.findItem(R.id.menu_cut_to_clipboard);
         MenuItem copy = menu.findItem(R.id.menu_copy_to_clipboard);
-        MenuItem pasteInto = menu.findItem(R.id.menu_paste_into_folder);
         MenuItem delete = menu.findItem(R.id.menu_delete);
-        MenuItem rename = menu.findItem(R.id.menu_rename);
 
-        copy.setEnabled(!selectionDetails.containsPartialFiles());
-        cut.setEnabled(
-                !selectionDetails.containsPartialFiles() && selectionDetails.canDelete());
-        updatePasteInto(pasteInto, selectionDetails);
-        updateRename(rename, selectionDetails);
-        updateDelete(delete, selectionDetails);
-
-        updateContextMenu(menu, directoryDetails);
+        final boolean canCopy =
+                selectionDetails.size() > 0 && !selectionDetails.containsPartialFiles();
+        final boolean canDelete = selectionDetails.canDelete();
+        cut.setEnabled(canCopy && canDelete);
+        copy.setEnabled(canCopy);
+        delete.setEnabled(canDelete);
     }
 
-    /** @See DirectoryFragment.onCreateContextMenu
+    /**
+     * @see DirectoryFragment#onCreateContextMenu
      *
      * Called when user tries to generate a context menu anchored to an empty pane.
-     * */
+     */
     public void updateContextMenuForContainer(Menu menu, DirectoryDetails directoryDetails) {
-        MenuItem cut = menu.findItem(R.id.menu_cut_to_clipboard);
-        MenuItem copy = menu.findItem(R.id.menu_copy_to_clipboard);
-        MenuItem pasteInto = menu.findItem(R.id.menu_paste_into_folder);
-        MenuItem delete = menu.findItem(R.id.menu_delete);
-        MenuItem rename = menu.findItem(R.id.menu_rename);
-
-        cut.setEnabled(false);
-        copy.setEnabled(false);
-        pasteInto.setEnabled(false);
-        rename.setEnabled(false);
-        delete.setEnabled(false);
-
-        updateContextMenu(menu, directoryDetails);
-    }
-
-    private void updateContextMenu(Menu menu, DirectoryDetails directoryDetails) {
-
-        MenuItem cut = menu.findItem(R.id.menu_cut_to_clipboard);
-        MenuItem copy = menu.findItem(R.id.menu_copy_to_clipboard);
         MenuItem paste = menu.findItem(R.id.menu_paste_from_clipboard);
-        MenuItem pasteInto = menu.findItem(R.id.menu_paste_into_folder);
-        MenuItem delete = menu.findItem(R.id.menu_delete);
-        MenuItem createDir = menu.findItem(R.id.menu_create_dir);
+        MenuItem selectAll = menu.findItem(R.id.menu_select_all);
 
-        updateCreateDir(createDir, directoryDetails);
-        paste.setEnabled(directoryDetails.hasItemsToPaste());
-
-        //Cut, Copy and Delete should always be visible
-        cut.setVisible(true);
-        copy.setVisible(true);
-        delete.setVisible(true);
-
-        // PasteInto should only show if it is enabled. If it's not enabled, Paste shows (regardless
-        // of whether it is enabled or not).
-        // Paste then hides itself whenever PasteInto is enabled/visible
-        pasteInto.setVisible(pasteInto.isEnabled());
-        paste.setVisible(!pasteInto.isVisible());
+        paste.setEnabled(directoryDetails.hasItemsToPaste() && directoryDetails.canCreateDoc());
+        updateSelectAll(selectAll);
     }
 
+    /**
+     * @see RootsFragment#onCreateContextMenu
+     */
     public void updateRootContextMenu(Menu menu, RootInfo root) {
         MenuItem settings = menu.findItem(R.id.menu_settings);
         MenuItem eject = menu.findItem(R.id.menu_eject_root);
@@ -162,10 +179,18 @@
         newWindow.setVisible(false);
     }
 
-    void updateOpen(MenuItem open, SelectionDetails selectionDetails) {
+    void updateOpenInActionMode(MenuItem open, SelectionDetails selectionDetails) {
         open.setVisible(false);
     }
 
+    void updateOpenWith(MenuItem openWith, SelectionDetails selectionDetails) {
+        openWith.setVisible(false);
+    }
+
+    void updateOpenInNewWindow(MenuItem openInNewWindow, SelectionDetails selectionDetails) {
+        openInNewWindow.setVisible(false);
+    }
+
     void updateShare(MenuItem share, SelectionDetails selectionDetails) {
         share.setVisible(false);
     }
@@ -187,10 +212,11 @@
     }
 
     void updatePasteInto(MenuItem pasteInto, SelectionDetails selectionDetails) {
-        pasteInto.setEnabled(false);
+        pasteInto.setVisible(false);
     }
 
-    abstract void updateSelectAll(MenuItem selectAll, SelectionDetails selectionDetails);
+    abstract void updateOpenInContextMenu(MenuItem open, SelectionDetails selectionDetails);
+    abstract void updateSelectAll(MenuItem selectAll);
     abstract void updateCreateDir(MenuItem createDir, DirectoryDetails directoryDetails);
 
     /**
@@ -199,6 +225,10 @@
     public interface SelectionDetails {
         boolean containsDirectories();
 
+        boolean containsFiles();
+
+        int size();
+
         boolean containsPartialFiles();
 
         // TODO: Update these to express characteristics instead of answering concrete questions,
@@ -229,6 +259,10 @@
             return false;
         }
 
+        public boolean canCreateDoc() {
+            return isInRecents() ? false : mActivity.getCurrentDirectory().isCreateSupported();
+        }
+
         public boolean isInRecents() {
             return mActivity.getCurrentDirectory() == null;
         }
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index b6a0009..4965ead 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -55,7 +55,6 @@
 import android.view.ContextMenu;
 import android.view.DragEvent;
 import android.view.LayoutInflater;
-import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.MotionEvent;
@@ -296,7 +295,7 @@
                     ? MultiSelectManager.MODE_MULTIPLE
                     : MultiSelectManager.MODE_SINGLE,
                 this::canSetSelectionState);
-        mSelectionMetadata = new SelectionMetadata(mSelectionMgr, mModel::getItem);
+        mSelectionMetadata = new SelectionMetadata(mModel::getItem);
         mSelectionMgr.addItemCallback(mSelectionMetadata);
 
         mModel.addUpdateListener(mAdapter);
@@ -438,23 +437,47 @@
             View v,
             ContextMenu.ContextMenuInfo menuInfo) {
         super.onCreateContextMenu(menu, v, menuInfo);
-        MenuInflater inflater = getActivity().getMenuInflater();
-        inflater.inflate(R.menu.context_menu, menu);
+        final MenuInflater inflater = getActivity().getMenuInflater();
 
-        menu.add(Menu.NONE, R.id.menu_create_dir, Menu.NONE, R.string.menu_create_dir);
-        menu.add(Menu.NONE, R.id.menu_delete, Menu.NONE, R.string.menu_delete);
-        menu.add(Menu.NONE, R.id.menu_rename, Menu.NONE, R.string.menu_rename);
-
-        boolean mouseOverFile = !(v == mRecView || v == mEmptyView);
-        if (mouseOverFile) {
-            mMenuManager.updateContextMenuForFile(
-                    menu,
-                    mSelectionMetadata,
-                    getBaseActivity().getDirectoryDetails());
-        } else {
-           mMenuManager.updateContextMenuForContainer(
-                   menu, getBaseActivity().getDirectoryDetails());
+        final String modelId = getModelId(v);
+        if (modelId == null) {
+            inflater.inflate(R.menu.container_context_menu, menu);
+            mMenuManager.updateContextMenuForContainer(
+                    menu, getBaseActivity().getDirectoryDetails());
+            return;
         }
+
+        final boolean hasDir = mSelectionMetadata.containsDirectories();
+        final boolean hasFile = mSelectionMetadata.containsFiles();
+        if (!hasDir && !hasFile) {
+            // User triggered a context menu on a doc without any selection. This is a legitimate
+            // case in pickers while user right clicks on an unselectable item.
+            final String mimeType = DocumentInfo.getCursorString(
+                    mModel.getItem(modelId), Document.COLUMN_MIME_TYPE);
+            if (Document.MIME_TYPE_DIR.equals(mimeType)) {
+                inflater.inflate(R.menu.dir_context_menu, menu);
+                mMenuManager.updateContextMenuForDirs(menu, mSelectionMetadata);
+            } else {
+                inflater.inflate(R.menu.file_context_menu, menu);
+                mMenuManager.updateContextMenuForFiles(menu, mSelectionMetadata);
+            }
+            return;
+        }
+
+        if (!hasDir) {
+            inflater.inflate(R.menu.file_context_menu, menu);
+            mMenuManager.updateContextMenuForFiles(menu, mSelectionMetadata);
+            return;
+        }
+
+        if (!hasFile) {
+            inflater.inflate(R.menu.dir_context_menu, menu);
+            mMenuManager.updateContextMenuForDirs(menu, mSelectionMetadata);
+            return;
+        }
+
+        inflater.inflate(R.menu.mixed_context_menu, menu);
+        mMenuManager.updateContextMenu(menu, mSelectionMetadata);
     }
 
     @Override
@@ -523,16 +546,6 @@
         return false;
     }
 
-    public void onDisplayStateChanged() {
-        updateDisplayState();
-    }
-
-    public void onSortOrderChanged() {
-        // Sort order is implemented as a sorting wrapper around directory
-        // results. So when sort order changes, we force a reload of the directory.
-        getLoaderManager().restartLoader(LOADER_ID, null, this);
-    }
-
     public void onViewModeChanged() {
         // Mode change is just visual change; no need to kick loader.
         updateDisplayState();
@@ -696,7 +709,12 @@
 
         // Model must be accessed in UI thread, since underlying cursor is not threadsafe.
         List<DocumentInfo> docs = mModel.getDocuments(selected);
-        BaseActivity.get(DirectoryFragment.this).onDocumentsPicked(docs);
+        BaseActivity activity = getBaseActivity();
+        if (docs.size() > 1) {
+            activity.onDocumentsPicked(docs);
+        } else {
+            activity.onDocumentPicked(docs.get(0), mModel);
+        }
     }
 
     private void shareDocuments(final Selection selected) {
diff --git a/src/com/android/documentsui/dirlist/MultiSelectManager.java b/src/com/android/documentsui/dirlist/MultiSelectManager.java
index 780c564..49eaa37 100644
--- a/src/com/android/documentsui/dirlist/MultiSelectManager.java
+++ b/src/com/android/documentsui/dirlist/MultiSelectManager.java
@@ -212,7 +212,10 @@
     private boolean setItemsSelectedQuietly(Iterable<String> ids, boolean selected) {
         boolean changed = false;
         for (String id: ids) {
-            final boolean itemChanged = selected ? mSelection.add(id) : mSelection.remove(id);
+            final boolean itemChanged =
+                    selected
+                    ? canSetState(id, true) && mSelection.add(id)
+                    : canSetState(id, false) && mSelection.remove(id);
             if (itemChanged) {
                 notifyItemStateChanged(id, selected);
             }
diff --git a/src/com/android/documentsui/dirlist/SelectionMetadata.java b/src/com/android/documentsui/dirlist/SelectionMetadata.java
index 7dbcacb..6a289f2 100644
--- a/src/com/android/documentsui/dirlist/SelectionMetadata.java
+++ b/src/com/android/documentsui/dirlist/SelectionMetadata.java
@@ -35,19 +35,18 @@
 
     private static final String TAG = "SelectionMetadata";
 
-    private final MultiSelectManager mSelectionMgr;
     private final Function<String, Cursor> mDocFinder;
 
+    private int mDirectoryCount = 0;
+    private int mFileCount = 0;
+
     // Partial files are files that haven't been fully downloaded.
     private int mPartialCount = 0;
-    private int mDirectoryCount = 0;
     private int mWritableDirectoryCount = 0;
     private int mNoDeleteCount = 0;
     private int mNoRenameCount = 0;
 
-    SelectionMetadata(
-            MultiSelectManager selectionMgr, Function<String, Cursor> docFinder) {
-        mSelectionMgr = selectionMgr;
+    SelectionMetadata(Function<String, Cursor> docFinder) {
         mDocFinder = docFinder;
     }
 
@@ -60,23 +59,27 @@
             return;
         }
 
+        final int delta = selected ? 1 : -1;
+
         final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
         if (MimePredicate.isDirectoryType(mimeType)) {
-            mDirectoryCount += selected ? 1 : -1;
+            mDirectoryCount += delta;
+        } else {
+            mFileCount += delta;
         }
 
         final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
         if ((docFlags & Document.FLAG_PARTIAL) != 0) {
-            mPartialCount += selected ? 1 : -1;
+            mPartialCount += delta;
         }
         if ((docFlags & Document.FLAG_DIR_SUPPORTS_CREATE) != 0) {
-            mWritableDirectoryCount += selected ? 1 : -1;
+            mWritableDirectoryCount += delta;
         }
         if ((docFlags & Document.FLAG_SUPPORTS_DELETE) == 0) {
-            mNoDeleteCount += selected ? 1 : -1;
+            mNoDeleteCount += delta;
         }
         if ((docFlags & Document.FLAG_SUPPORTS_RENAME) == 0) {
-            mNoRenameCount += selected ? 1 : -1;
+            mNoRenameCount += delta;
         }
     }
 
@@ -86,23 +89,32 @@
     }
 
     @Override
+    public boolean containsFiles() {
+        return mFileCount > 0;
+    }
+
+    @Override
+    public int size() {
+        return mDirectoryCount + mFileCount;
+    }
+
+    @Override
     public boolean containsPartialFiles() {
         return mPartialCount > 0;
     }
 
     @Override
     public boolean canDelete() {
-        return mNoDeleteCount == 0;
+        return size() > 0 && mNoDeleteCount == 0;
     }
 
     @Override
     public boolean canRename() {
-        return mNoRenameCount == 0 && mSelectionMgr.getSelection().size() == 1;
+        return mNoRenameCount == 0 && size() == 1;
     }
 
     @Override
     public boolean canPasteInto() {
-        return mDirectoryCount == 1 && mWritableDirectoryCount == 1
-                && mSelectionMgr.getSelection().size() == 1;
+        return mDirectoryCount == 1 && mWritableDirectoryCount == 1 && size() == 1;
     }
 }
diff --git a/tests/src/com/android/documentsui/DocumentsMenuManagerTest.java b/tests/src/com/android/documentsui/DocumentsMenuManagerTest.java
index 5272e43..f08e915 100644
--- a/tests/src/com/android/documentsui/DocumentsMenuManagerTest.java
+++ b/tests/src/com/android/documentsui/DocumentsMenuManagerTest.java
@@ -43,6 +43,8 @@
 
     private TestMenu testMenu;
     private TestMenuItem open;
+    private TestMenuItem openInNewWindow;
+    private TestMenuItem openWith;
     private TestMenuItem share;
     private TestMenuItem delete;
     private TestMenuItem rename;
@@ -68,6 +70,8 @@
     public void setUp() {
         testMenu = TestMenu.create();
         open = testMenu.findItem(R.id.menu_open);
+        openInNewWindow = testMenu.findItem(R.id.menu_open_in_new_window);
+        openWith = testMenu.findItem(R.id.menu_open_with);
         share = testMenu.findItem(R.id.menu_share);
         delete = testMenu.findItem(R.id.menu_delete);
         rename =  testMenu.findItem(R.id.menu_rename);
@@ -149,8 +153,8 @@
 
     @Test
     public void testOptionMenu_canCreateDirectory() {
-        directoryDetails.canCreateDirectory = true;
         DocumentsMenuManager mgr = new DocumentsMenuManager(testSearchManager, state);
+        directoryDetails.canCreateDirectory = true;
         mgr.updateOptionMenu(testMenu, directoryDetails);
 
         createDir.assertEnabled();
@@ -169,8 +173,8 @@
 
     @Test
     public void testOptionMenu_inRecents() {
-        directoryDetails.isInRecents = true;
         DocumentsMenuManager mgr = new DocumentsMenuManager(testSearchManager, state);
+        directoryDetails.isInRecents = true;
         mgr.updateOptionMenu(testMenu, directoryDetails);
 
         grid.assertInvisible();
@@ -181,27 +185,22 @@
     public void testContextMenu_EmptyArea() {
         DocumentsMenuManager mgr = new DocumentsMenuManager(testSearchManager, state);
         mgr.updateContextMenuForContainer(testMenu, directoryDetails);
-        cut.assertVisible();
-        copy.assertVisible();
-        cut.assertDisabled();
-        copy.assertDisabled();
+        selectAll.assertVisible();
         paste.assertVisible();
-        pasteInto.assertInvisible();
         createDir.assertVisible();
-        delete.assertVisible();
     }
 
     @Test
     public void testContextMenu_OnFile() {
         DocumentsMenuManager mgr = new DocumentsMenuManager(testSearchManager, state);
-        mgr.updateContextMenuForFile(testMenu, selectionDetails, directoryDetails);
+        mgr.updateContextMenuForFiles(testMenu, selectionDetails);
+        // We don't want share in pickers.
+        share.assertInvisible();
+        // We don't want openWith in pickers.
+        openWith.assertInvisible();
         cut.assertVisible();
         copy.assertVisible();
-        paste.assertVisible();
-        pasteInto.assertInvisible();
-        paste.assertDisabled();
         rename.assertInvisible();
-        createDir.assertVisible();
         delete.assertVisible();
     }
 
@@ -209,15 +208,14 @@
     public void testContextMenu_OnDirectory() {
         DocumentsMenuManager mgr = new DocumentsMenuManager(testSearchManager, state);
         selectionDetails.canPasteInto = true;
-        mgr.updateContextMenuForFile(testMenu, selectionDetails, directoryDetails);
+        mgr.updateContextMenuForDirs(testMenu, selectionDetails);
+        // We don't want openInNewWindow in pickers
+        openInNewWindow.assertInvisible();
         cut.assertVisible();
         copy.assertVisible();
         // Doesn't matter if directory is selected, we don't want pasteInto for DocsActivity
-        paste.assertVisible();
         pasteInto.assertInvisible();
-        pasteInto.assertDisabled();
         rename.assertInvisible();
-        createDir.assertVisible();
         delete.assertVisible();
     }
 
@@ -232,8 +230,8 @@
 
     @Test
     public void testRootContextMenu_hasRootSettings() {
-        testRootInfo.flags = Root.FLAG_HAS_SETTINGS;
         DocumentsMenuManager mgr = new DocumentsMenuManager(testSearchManager, state);
+        testRootInfo.flags = Root.FLAG_HAS_SETTINGS;
         mgr.updateRootContextMenu(testMenu, testRootInfo);
 
         settings.assertInvisible();
@@ -241,8 +239,8 @@
 
     @Test
     public void testRootContextMenu_canEject() {
-        testRootInfo.flags = Root.FLAG_SUPPORTS_EJECT;
         DocumentsMenuManager mgr = new DocumentsMenuManager(testSearchManager, state);
+        testRootInfo.flags = Root.FLAG_SUPPORTS_EJECT;
         mgr.updateRootContextMenu(testMenu, testRootInfo);
 
         eject.assertInvisible();
diff --git a/tests/src/com/android/documentsui/FilesMenuManagerTest.java b/tests/src/com/android/documentsui/FilesMenuManagerTest.java
index 3924ece..cbec819 100644
--- a/tests/src/com/android/documentsui/FilesMenuManagerTest.java
+++ b/tests/src/com/android/documentsui/FilesMenuManagerTest.java
@@ -40,6 +40,7 @@
 
     private TestMenu testMenu;
     private TestMenuItem rename;
+    private TestMenuItem selectAll;
     private TestMenuItem moveTo;
     private TestMenuItem copyTo;
     private TestMenuItem share;
@@ -47,6 +48,9 @@
     private TestMenuItem createDir;
     private TestMenuItem settings;
     private TestMenuItem newWindow;
+    private TestMenuItem open;
+    private TestMenuItem openWith;
+    private TestMenuItem openInNewWindow;
     private TestMenuItem cut;
     private TestMenuItem copy;
     private TestMenuItem paste;
@@ -63,6 +67,7 @@
     public void setUp() {
         testMenu = TestMenu.create();
         rename = testMenu.findItem(R.id.menu_rename);
+        selectAll = testMenu.findItem(R.id.menu_select_all);
         moveTo = testMenu.findItem(R.id.menu_move_to);
         copyTo = testMenu.findItem(R.id.menu_copy_to);
         share = testMenu.findItem(R.id.menu_share);
@@ -70,6 +75,9 @@
         createDir = testMenu.findItem(R.id.menu_create_dir);
         settings = testMenu.findItem(R.id.menu_settings);
         newWindow = testMenu.findItem(R.id.menu_new_window);
+        open = testMenu.findItem(R.id.menu_open);
+        openWith = testMenu.findItem(R.id.menu_open_with);
+        openInNewWindow = testMenu.findItem(R.id.menu_open_in_new_window);
         cut = testMenu.findItem(R.id.menu_cut_to_clipboard);
         copy = testMenu.findItem(R.id.menu_copy_to_clipboard);
         paste = testMenu.findItem(R.id.menu_paste_from_clipboard);
@@ -197,60 +205,79 @@
     public void testContextMenu_EmptyArea() {
         FilesMenuManager mgr = new FilesMenuManager(testSearchManager, state);
         mgr.updateContextMenuForContainer(testMenu, directoryDetails);
+
+        selectAll.assertVisible();
+        paste.assertVisible();
+        createDir.assertVisible();
+    }
+
+    @Test
+    public void testContextMenu_OnFile() {
+        selectionDetails.size = 1;
+        FilesMenuManager mgr = new FilesMenuManager(testSearchManager, state);
+        mgr.updateContextMenuForFiles(testMenu, selectionDetails);
+        open.assertVisible();
+        open.assertEnabled();
+        openWith.assertVisible();
+        openWith.assertEnabled();
         cut.assertVisible();
         copy.assertVisible();
-        cut.assertDisabled();
-        copy.assertDisabled();
-        paste.assertVisible();
-        pasteInto.assertInvisible();
+        rename.assertVisible();
         createDir.assertVisible();
         delete.assertVisible();
     }
 
     @Test
-    public void testContextMenu_OnFile() {
+    public void testContextMenu_OnMultipleFiles() {
         FilesMenuManager mgr = new FilesMenuManager(testSearchManager, state);
-        mgr.updateContextMenuForFile(testMenu, selectionDetails, directoryDetails);
-        cut.assertVisible();
-        copy.assertVisible();
-        paste.assertVisible();
-        pasteInto.assertInvisible();
-        paste.assertDisabled();
-        rename.assertVisible();
-        createDir.assertVisible();
-        delete.assertVisible();
+        selectionDetails.size = 3;
+        mgr.updateContextMenuForFiles(testMenu, selectionDetails);
+        open.assertVisible();
+        open.assertDisabled();
+        openWith.assertVisible();
+        openWith.assertDisabled();
     }
 
     @Test
     public void testContextMenu_OnWritableDirectory() {
         FilesMenuManager mgr = new FilesMenuManager(testSearchManager, state);
+        selectionDetails.size = 1;
         selectionDetails.canPasteInto = true;
-        mgr.updateContextMenuForFile(testMenu, selectionDetails, directoryDetails);
+        mgr.updateContextMenuForDirs(testMenu, selectionDetails);
+        openInNewWindow.assertVisible();
+        openInNewWindow.assertEnabled();
         cut.assertVisible();
         copy.assertVisible();
-        paste.assertInvisible();
         pasteInto.assertVisible();
         pasteInto.assertEnabled();
         rename.assertVisible();
-        createDir.assertVisible();
         delete.assertVisible();
     }
 
     @Test
     public void testContextMenu_OnNonWritableDirectory() {
         FilesMenuManager mgr = new FilesMenuManager(testSearchManager, state);
+        selectionDetails.size = 1;
         selectionDetails.canPasteInto = false;
-        mgr.updateContextMenuForFile(testMenu, selectionDetails, directoryDetails);
+        mgr.updateContextMenuForDirs(testMenu, selectionDetails);
+        openInNewWindow.assertVisible();
+        openInNewWindow.assertEnabled();
         cut.assertVisible();
         copy.assertVisible();
-        paste.assertVisible();
-        pasteInto.assertInvisible();
+        pasteInto.assertVisible();
         pasteInto.assertDisabled();
         rename.assertVisible();
-        createDir.assertVisible();
         delete.assertVisible();
     }
 
+    @Test
+    public void testContextMenu_OnMultipleDirectories() {
+        FilesMenuManager mgr = new FilesMenuManager(testSearchManager, state);
+        selectionDetails.size = 3;
+        mgr.updateContextMenuForDirs(testMenu, selectionDetails);
+        openInNewWindow.assertVisible();
+        openInNewWindow.assertDisabled();
+    }
 
     @Test
     public void testRootContextMenu() {
diff --git a/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java b/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
index 1c7b863..fef5672 100644
--- a/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
+++ b/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
@@ -29,6 +29,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -87,6 +88,24 @@
     }
 
     @Test
+    public void testSetItemsSelected() {
+        mManager.setItemsSelected(getStringIds(6, 7, 8), true);
+
+        mSelection.assertRangeSelected(6, 8);
+    }
+
+    @Test
+    public void testSetItemsSelected_SkipUnselectableItem() {
+        mIgnored.add(ITEMS.get(7));
+
+        mManager.setItemsSelected(getStringIds(6, 7, 8), true);
+
+        mSelection.assertSelected(6);
+        mSelection.assertNotSelected(7);
+        mSelection.assertSelected(8);
+    }
+
+    @Test
     public void testRangeSelection() {
         mManager.startRangeSelection(15);
         mManager.snapRangeSelection(19);
@@ -283,4 +302,12 @@
 
         return ids;
     }
+
+    private static Iterable<String> getStringIds(int... ids) {
+        List<String> stringIds = new ArrayList<>(ids.length);
+        for (int id : ids) {
+            stringIds.add(ITEMS.get(id));
+        }
+        return stringIds;
+    }
 }
diff --git a/tests/src/com/android/documentsui/testing/TestMenu.java b/tests/src/com/android/documentsui/testing/TestMenu.java
index 718b2dc..d61db11 100644
--- a/tests/src/com/android/documentsui/testing/TestMenu.java
+++ b/tests/src/com/android/documentsui/testing/TestMenu.java
@@ -36,7 +36,10 @@
     private SparseArray<TestMenuItem> items = new SparseArray<>();
 
     public static TestMenu create() {
-        return create(R.id.menu_open,
+        return create(
+                R.id.menu_open,
+                R.id.menu_open_in_new_window,
+                R.id.menu_open_with,
                 R.id.menu_rename,
                 R.id.menu_move_to,
                 R.id.menu_copy_to,
@@ -62,7 +65,7 @@
         menu.items = new SparseArray<>();
         for (int id : ids) {
             TestMenuItem item = TestMenuItem.create(id);
-             menu.addMenuItem(id, item);
+            menu.addMenuItem(id, item);
         }
         return menu;
     }
diff --git a/tests/src/com/android/documentsui/testing/TestMenuItem.java b/tests/src/com/android/documentsui/testing/TestMenuItem.java
index 6314b7b..110617b 100644
--- a/tests/src/com/android/documentsui/testing/TestMenuItem.java
+++ b/tests/src/com/android/documentsui/testing/TestMenuItem.java
@@ -43,6 +43,10 @@
         final TestMenuItem mockMenuItem = Mockito.mock(TestMenuItem.class,
                 Mockito.withSettings().defaultAnswer(Mockito.CALLS_REAL_METHODS));
 
+        // By default all menu items are enabled and visible.
+        mockMenuItem.enabled = true;
+        mockMenuItem.visible = true;
+
         return mockMenuItem;
     }
 
diff --git a/tests/src/com/android/documentsui/testing/TestSelectionDetails.java b/tests/src/com/android/documentsui/testing/TestSelectionDetails.java
index 795ef7a..e7f9b6c 100644
--- a/tests/src/com/android/documentsui/testing/TestSelectionDetails.java
+++ b/tests/src/com/android/documentsui/testing/TestSelectionDetails.java
@@ -23,10 +23,12 @@
  */
 public class TestSelectionDetails implements SelectionDetails {
 
+    public int size;
     public boolean canRename;
     public boolean canDelete;
     public boolean containPartial;
     public boolean containDirectories;
+    public boolean containFiles;
     public boolean canPasteInto;
 
      @Override
@@ -35,6 +37,11 @@
      }
 
      @Override
+     public boolean containsFiles() {
+         return containFiles;
+     }
+
+     @Override
      public boolean containsDirectories() {
          return containDirectories;
      }
@@ -53,4 +60,9 @@
     public boolean canPasteInto() {
         return canPasteInto;
     }
+
+    @Override
+    public int size() {
+        return size;
+    }
  }
\ No newline at end of file