[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/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;
     }
 }