/* * 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.KeyboardShortcutGroup; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import androidx.annotation.VisibleForTesting; import androidx.fragment.app.Fragment; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.Menus; import com.android.documentsui.base.RootInfo; import com.android.documentsui.base.State; import com.android.documentsui.dirlist.DirectoryFragment; import com.android.documentsui.queries.SearchViewManager; import com.android.documentsui.sidebar.RootsFragment; import java.util.List; import java.util.function.IntFunction; import java.util.function.IntSupplier; public abstract class MenuManager { private final static String TAG = "MenuManager"; protected final SearchViewManager mSearchManager; protected final State mState; protected final DirectoryDetails mDirDetails; protected final IntSupplier mFilesCountSupplier; protected Menu mOptionMenu; public MenuManager( SearchViewManager searchManager, State displayState, DirectoryDetails dirDetails, IntSupplier filesCountSupplier) { mSearchManager = searchManager; mState = displayState; mDirDetails = dirDetails; mFilesCountSupplier = filesCountSupplier; } /** @see ActionModeController */ public void updateActionMenu(Menu menu, SelectionDetails selection) { updateOpenWith(menu.findItem(R.id.action_menu_open_with), selection); updateDelete(menu.findItem(R.id.action_menu_delete), selection); updateShare(menu.findItem(R.id.action_menu_share), selection); updateRename(menu.findItem(R.id.action_menu_rename), selection); updateSelect(menu.findItem(R.id.action_menu_select), selection); updateSelectAll(menu.findItem(R.id.action_menu_select_all), selection); updateDeselectAll(menu.findItem(R.id.action_menu_deselect_all), selection); updateMoveTo(menu.findItem(R.id.action_menu_move_to), selection); updateCopyTo(menu.findItem(R.id.action_menu_copy_to), selection); updateCompress(menu.findItem(R.id.action_menu_compress), selection); updateExtractTo(menu.findItem(R.id.action_menu_extract_to), selection); updateInspect(menu.findItem(R.id.action_menu_inspect), selection); updateViewInOwner(menu.findItem(R.id.action_menu_view_in_owner), selection); updateSort(menu.findItem(R.id.action_menu_sort)); Menus.disableHiddenItems(menu); } /** @see BaseActivity#onPrepareOptionsMenu */ public void updateOptionMenu(Menu menu) { mOptionMenu = menu; updateOptionMenu(); } public void updateOptionMenu() { if (mOptionMenu == null) { return; } updateCreateDir(mOptionMenu.findItem(R.id.option_menu_create_dir)); updateSettings(mOptionMenu.findItem(R.id.option_menu_settings)); updateSelectAll(mOptionMenu.findItem(R.id.option_menu_select_all)); updateNewWindow(mOptionMenu.findItem(R.id.option_menu_new_window)); updateDebug(mOptionMenu.findItem(R.id.option_menu_debug)); updateInspect(mOptionMenu.findItem(R.id.option_menu_inspect)); updateSort(mOptionMenu.findItem(R.id.option_menu_sort)); updateLauncher(mOptionMenu.findItem(R.id.option_menu_launcher)); updateShowHiddenFiles(mOptionMenu.findItem(R.id.option_menu_show_hidden_files)); Menus.disableHiddenItems(mOptionMenu); mSearchManager.updateMenu(); } public void updateSubMenu(Menu menu) { updateModePicker(menu.findItem(R.id.sub_menu_grid), menu.findItem(R.id.sub_menu_list)); } public void updateModel(Model model) {} /** * Called when we needs {@link MenuManager} to ask Android to show context menu for us. * {@link MenuManager} can choose to defeat this request. * * {@link #inflateContextMenuForDocs} and {@link #inflateContextMenuForContainer} are called * afterwards when Android asks us to provide the content of context menus, so they're not * correct locations to suppress context menus. */ public void showContextMenu(Fragment f, View v, float x, float y) { // Pickers don't have any context menu at this moment. } /** * Called when container context menu needs to be inflated. * * @param menu context menu from activity or fragment * @param inflater the MenuInflater * @param selectionDetails selection of files */ public void inflateContextMenuForContainer( Menu menu, MenuInflater inflater, SelectionDetails selectionDetails) { throw new UnsupportedOperationException("Pickers don't allow context menu."); } public void inflateContextMenuForDocs( Menu menu, MenuInflater inflater, SelectionDetails selectionDetails) { throw new UnsupportedOperationException("Pickers don't allow context menu."); } /** * @see DirectoryFragment#onCreateContextMenu * * 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 */ @VisibleForTesting public void updateContextMenuForFiles(Menu menu, SelectionDetails selectionDetails) { assert selectionDetails != null; MenuItem share = menu.findItem(R.id.dir_menu_share); MenuItem open = menu.findItem(R.id.dir_menu_open); MenuItem openWith = menu.findItem(R.id.dir_menu_open_with); MenuItem rename = menu.findItem(R.id.dir_menu_rename); MenuItem viewInOwner = menu.findItem(R.id.dir_menu_view_in_owner); updateShare(share, selectionDetails); updateOpenInContextMenu(open, selectionDetails); updateOpenWith(openWith, selectionDetails); updateRename(rename, selectionDetails); updateViewInOwner(viewInOwner, 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 */ @VisibleForTesting public void updateContextMenuForDirs(Menu menu, SelectionDetails selectionDetails) { assert selectionDetails != null; MenuItem openInNewWindow = menu.findItem(R.id.dir_menu_open_in_new_window); MenuItem rename = menu.findItem(R.id.dir_menu_rename); MenuItem pasteInto = menu.findItem(R.id.dir_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. */ @VisibleForTesting public void updateContextMenu(Menu menu, SelectionDetails selectionDetails) { assert selectionDetails != null; MenuItem cut = menu.findItem(R.id.dir_menu_cut_to_clipboard); MenuItem copy = menu.findItem(R.id.dir_menu_copy_to_clipboard); MenuItem delete = menu.findItem(R.id.dir_menu_delete); MenuItem inspect = menu.findItem(R.id.dir_menu_inspect); final boolean canCopy = selectionDetails.size() > 0 && !selectionDetails.containsPartialFiles(); final boolean canDelete = selectionDetails.canDelete(); Menus.setEnabledAndVisible(cut, canCopy && canDelete); Menus.setEnabledAndVisible(copy, canCopy); Menus.setEnabledAndVisible(delete, canDelete); Menus.setEnabledAndVisible(inspect, selectionDetails.size() == 1); } /** * @see DirectoryFragment#onCreateContextMenu * * Called when user tries to generate a context menu anchored to an empty pane. */ @VisibleForTesting public void updateContextMenuForContainer(Menu menu, SelectionDetails selectionDetails) { MenuItem paste = menu.findItem(R.id.dir_menu_paste_from_clipboard); MenuItem selectAll = menu.findItem(R.id.dir_menu_select_all); MenuItem deselectAll = menu.findItem(R.id.dir_menu_deselect_all); MenuItem createDir = menu.findItem(R.id.dir_menu_create_dir); MenuItem inspect = menu.findItem(R.id.dir_menu_inspect); Menus.setEnabledAndVisible(paste, mDirDetails.hasItemsToPaste() && mDirDetails.canCreateDoc()); updateSelectAll(selectAll, selectionDetails); updateDeselectAll(deselectAll, selectionDetails); updateCreateDir(createDir); updateInspect(inspect); } /** * @see RootsFragment#onCreateContextMenu */ public void updateRootContextMenu(Menu menu, RootInfo root, DocumentInfo docInfo) { MenuItem eject = menu.findItem(R.id.root_menu_eject_root); MenuItem pasteInto = menu.findItem(R.id.root_menu_paste_into_folder); MenuItem openInNewWindow = menu.findItem(R.id.root_menu_open_in_new_window); MenuItem settings = menu.findItem(R.id.root_menu_settings); updateEject(eject, root); updatePasteInto(pasteInto, root, docInfo); updateOpenInNewWindow(openInNewWindow, root); updateSettings(settings, root); } public abstract void updateKeyboardShortcutsMenu( List data, IntFunction stringSupplier); protected void updateModePicker(MenuItem grid, MenuItem list) { // The order of enabling disabling menu item in wrong order removed accessibility focus. if (mState.derivedMode != State.MODE_LIST) { Menus.setEnabledAndVisible(list, mState.derivedMode != State.MODE_LIST); Menus.setEnabledAndVisible(grid, mState.derivedMode != State.MODE_GRID); } else { Menus.setEnabledAndVisible(grid, mState.derivedMode != State.MODE_GRID); Menus.setEnabledAndVisible(list, mState.derivedMode != State.MODE_LIST); } } protected void updateShowHiddenFiles(MenuItem showHidden) { Menus.setEnabledAndVisible(showHidden, true); showHidden.setTitle(mState.showHiddenFiles ? R.string.menu_hide_hidden_files : R.string.menu_show_hidden_files); } protected void updateSort(MenuItem sort) { Menus.setEnabledAndVisible(sort, true); } protected void updateDebug(MenuItem debug) { Menus.setEnabledAndVisible(debug, mState.debugMode); } protected void updateSettings(MenuItem settings) { Menus.setEnabledAndVisible(settings, false); } protected void updateSettings(MenuItem settings, RootInfo root) { Menus.setEnabledAndVisible(settings, false); } protected void updateEject(MenuItem eject, RootInfo root) { Menus.setEnabledAndVisible(eject, false); } protected void updateNewWindow(MenuItem newWindow) { Menus.setEnabledAndVisible(newWindow, false); } protected void updateSelect(MenuItem select, SelectionDetails selectionDetails) { Menus.setEnabledAndVisible(select, false); } protected void updateOpenWith(MenuItem openWith, SelectionDetails selectionDetails) { Menus.setEnabledAndVisible(openWith, false); } protected void updateOpenInNewWindow( MenuItem openInNewWindow, SelectionDetails selectionDetails) { Menus.setEnabledAndVisible(openInNewWindow, false); } protected void updateOpenInNewWindow( MenuItem openInNewWindow, RootInfo root) { Menus.setEnabledAndVisible(openInNewWindow, false); } protected void updateShare(MenuItem share, SelectionDetails selectionDetails) { Menus.setEnabledAndVisible(share, false); } protected void updateDelete(MenuItem delete, SelectionDetails selectionDetails) { Menus.setEnabledAndVisible(delete, false); } protected void updateRename(MenuItem rename, SelectionDetails selectionDetails) { Menus.setEnabledAndVisible(rename, false); } /** * This method is called for standard activity option menu as opposed * to when there is a selection. */ protected void updateInspect(MenuItem inspector) { Menus.setEnabledAndVisible(inspector, false); } /** * This method is called for action mode, when a selection exists. */ protected void updateInspect(MenuItem inspect, SelectionDetails selectionDetails) { Menus.setEnabledAndVisible(inspect, false); } protected void updateViewInOwner(MenuItem view, SelectionDetails selectionDetails) { Menus.setEnabledAndVisible(view, false); } protected void updateMoveTo(MenuItem moveTo, SelectionDetails selectionDetails) { Menus.setEnabledAndVisible(moveTo, false); } protected void updateCopyTo(MenuItem copyTo, SelectionDetails selectionDetails) { Menus.setEnabledAndVisible(copyTo, false); } protected void updateCompress(MenuItem compress, SelectionDetails selectionDetails) { Menus.setEnabledAndVisible(compress, false); } protected void updateExtractTo(MenuItem extractTo, SelectionDetails selectionDetails) { Menus.setEnabledAndVisible(extractTo, false); } protected void updatePasteInto(MenuItem pasteInto, SelectionDetails selectionDetails) { Menus.setEnabledAndVisible(pasteInto, false); } protected void updatePasteInto(MenuItem pasteInto, RootInfo root, DocumentInfo docInfo) { Menus.setEnabledAndVisible(pasteInto, false); } protected void updateOpenInContextMenu(MenuItem open, SelectionDetails selectionDetails) { Menus.setEnabledAndVisible(open, false); } protected void updateLauncher(MenuItem launcher) { Menus.setEnabledAndVisible(launcher, false); } protected abstract void updateSelectAll(MenuItem selectAll); protected abstract void updateSelectAll(MenuItem selectAll, SelectionDetails selectionDetails); protected abstract void updateDeselectAll( MenuItem deselectAll, SelectionDetails selectionDetails); protected abstract void updateCreateDir(MenuItem createDir); /** * Access to meta data about the selection. */ public interface SelectionDetails { boolean containsDirectories(); boolean containsFiles(); int size(); boolean containsPartialFiles(); boolean containsFilesInArchive(); // 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(); boolean canPasteInto(); boolean canExtract(); boolean canOpenWith(); boolean canViewInOwner(); } public static class DirectoryDetails { private final BaseActivity mActivity; public DirectoryDetails(BaseActivity activity) { mActivity = activity; } public boolean hasRootSettings() { return mActivity.getCurrentRoot().hasSettings(); } public boolean hasItemsToPaste() { return false; } public boolean canCreateDoc() { return isInRecents() ? false : mActivity.getCurrentDirectory().isCreateSupported(); } public boolean isInRecents() { return mActivity.isInRecents(); } public boolean canCreateDirectory() { return mActivity.canCreateDirectory(); } public boolean canInspectDirectory() { return mActivity.canInspectDirectory() && !isInRecents(); } } }