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