1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.documentsui; 18 19 import android.app.Fragment; 20 import android.view.KeyboardShortcutGroup; 21 import android.view.Menu; 22 import android.view.MenuInflater; 23 import android.view.MenuItem; 24 import android.view.View; 25 26 import com.android.documentsui.base.DocumentInfo; 27 import com.android.documentsui.base.Menus; 28 import com.android.documentsui.base.RootInfo; 29 import com.android.documentsui.base.State; 30 import com.android.documentsui.dirlist.DirectoryFragment; 31 import com.android.documentsui.files.FilesActivity; 32 import com.android.documentsui.queries.SearchViewManager; 33 import com.android.documentsui.sidebar.RootsFragment; 34 import com.android.internal.annotations.VisibleForTesting; 35 36 import java.util.List; 37 import java.util.function.IntFunction; 38 39 public abstract class MenuManager { 40 41 final protected SearchViewManager mSearchManager; 42 final protected State mState; 43 final protected DirectoryDetails mDirDetails; 44 MenuManager( SearchViewManager searchManager, State displayState, DirectoryDetails dirDetails)45 public MenuManager( 46 SearchViewManager searchManager, 47 State displayState, 48 DirectoryDetails dirDetails) { 49 mSearchManager = searchManager; 50 mState = displayState; 51 mDirDetails = dirDetails; 52 } 53 54 /** @see ActionModeController */ updateActionMenu(Menu menu, SelectionDetails selection)55 public void updateActionMenu(Menu menu, SelectionDetails selection) { 56 updateOpenInActionMode(menu.findItem(R.id.menu_open), selection); 57 updateOpenWith(menu.findItem(R.id.menu_open_with), selection); 58 updateDelete(menu.findItem(R.id.menu_delete), selection); 59 updateShare(menu.findItem(R.id.menu_share), selection); 60 updateRename(menu.findItem(R.id.menu_rename), selection); 61 updateSelectAll(menu.findItem(R.id.menu_select_all)); 62 updateMoveTo(menu.findItem(R.id.menu_move_to), selection); 63 updateCopyTo(menu.findItem(R.id.menu_copy_to), selection); 64 updateCompress(menu.findItem(R.id.menu_compress), selection); 65 updateExtractTo(menu.findItem(R.id.menu_extract_to), selection); 66 updateViewInOwner(menu.findItem(R.id.menu_view_in_owner), selection); 67 68 Menus.disableHiddenItems(menu); 69 } 70 71 /** @see BaseActivity#onPrepareOptionsMenu */ updateOptionMenu(Menu menu)72 public void updateOptionMenu(Menu menu) { 73 updateCreateDir(menu.findItem(R.id.menu_create_dir)); 74 updateSettings(menu.findItem(R.id.menu_settings)); 75 updateSelectAll(menu.findItem(R.id.menu_select_all)); 76 updateNewWindow(menu.findItem(R.id.menu_new_window)); 77 updateModePicker(menu.findItem(R.id.menu_grid), menu.findItem(R.id.menu_list)); 78 updateAdvanced(menu.findItem(R.id.menu_advanced)); 79 updateDebug(menu.findItem(R.id.menu_debug)); 80 81 Menus.disableHiddenItems(menu); 82 } 83 84 /** 85 * Called when we needs {@link MenuManager} to ask Android to show context menu for us. 86 * {@link MenuManager} can choose to defeat this request. 87 * 88 * {@link #inflateContextMenuForDocs} and {@link #inflateContextMenuForContainer} are called 89 * afterwards when Android asks us to provide the content of context menus, so they're not 90 * correct locations to suppress context menus. 91 */ showContextMenu(Fragment f, View v, float x, float y)92 public void showContextMenu(Fragment f, View v, float x, float y) { 93 // Pickers don't have any context menu at this moment. 94 } 95 inflateContextMenuForContainer(Menu menu, MenuInflater inflater)96 public void inflateContextMenuForContainer(Menu menu, MenuInflater inflater) { 97 throw new UnsupportedOperationException("Pickers don't allow context menu."); 98 } 99 inflateContextMenuForDocs( Menu menu, MenuInflater inflater, SelectionDetails selectionDetails)100 public void inflateContextMenuForDocs( 101 Menu menu, MenuInflater inflater, SelectionDetails selectionDetails) { 102 throw new UnsupportedOperationException("Pickers don't allow context menu."); 103 } 104 105 /** 106 * @see DirectoryFragment#onCreateContextMenu 107 * 108 * Called when user tries to generate a context menu anchored to a file when the selection 109 * doesn't contain any folder. 110 * 111 * @param selectionDetails 112 * containsFiles may return false because this may be called when user right clicks on an 113 * unselectable item in pickers 114 */ 115 @VisibleForTesting updateContextMenuForFiles(Menu menu, SelectionDetails selectionDetails)116 public void updateContextMenuForFiles(Menu menu, SelectionDetails selectionDetails) { 117 assert(selectionDetails != null); 118 119 MenuItem share = menu.findItem(R.id.menu_share); 120 MenuItem open = menu.findItem(R.id.menu_open); 121 MenuItem openWith = menu.findItem(R.id.menu_open_with); 122 MenuItem rename = menu.findItem(R.id.menu_rename); 123 MenuItem viewInOwner = menu.findItem(R.id.menu_view_in_owner); 124 125 updateShare(share, selectionDetails); 126 updateOpenInContextMenu(open, selectionDetails); 127 updateOpenWith(openWith, selectionDetails); 128 updateRename(rename, selectionDetails); 129 updateViewInOwner(viewInOwner, selectionDetails); 130 131 updateContextMenu(menu, selectionDetails); 132 } 133 134 /** 135 * @see DirectoryFragment#onCreateContextMenu 136 * 137 * Called when user tries to generate a context menu anchored to a folder when the selection 138 * doesn't contain any file. 139 * 140 * @param selectionDetails 141 * containDirectories may return false because this may be called when user right clicks on 142 * an unselectable item in pickers 143 */ 144 @VisibleForTesting updateContextMenuForDirs(Menu menu, SelectionDetails selectionDetails)145 public void updateContextMenuForDirs(Menu menu, SelectionDetails selectionDetails) { 146 assert(selectionDetails != null); 147 148 MenuItem openInNewWindow = menu.findItem(R.id.menu_open_in_new_window); 149 MenuItem rename = menu.findItem(R.id.menu_rename); 150 MenuItem pasteInto = menu.findItem(R.id.menu_paste_into_folder); 151 152 updateOpenInNewWindow(openInNewWindow, selectionDetails); 153 updateRename(rename, selectionDetails); 154 updatePasteInto(pasteInto, selectionDetails); 155 156 updateContextMenu(menu, selectionDetails); 157 } 158 159 /** 160 * @see DirectoryFragment#onCreateContextMenu 161 * 162 * Update shared context menu items of both files and folders context menus. 163 */ 164 @VisibleForTesting updateContextMenu(Menu menu, SelectionDetails selectionDetails)165 public void updateContextMenu(Menu menu, SelectionDetails selectionDetails) { 166 assert(selectionDetails != null); 167 168 MenuItem cut = menu.findItem(R.id.menu_cut_to_clipboard); 169 MenuItem copy = menu.findItem(R.id.menu_copy_to_clipboard); 170 MenuItem delete = menu.findItem(R.id.menu_delete); 171 172 final boolean canCopy = 173 selectionDetails.size() > 0 && !selectionDetails.containsPartialFiles(); 174 final boolean canDelete = selectionDetails.canDelete(); 175 cut.setEnabled(canCopy && canDelete); 176 copy.setEnabled(canCopy); 177 delete.setEnabled(canDelete); 178 } 179 180 /** 181 * @see DirectoryFragment#onCreateContextMenu 182 * 183 * Called when user tries to generate a context menu anchored to an empty pane. 184 */ 185 @VisibleForTesting updateContextMenuForContainer(Menu menu)186 public void updateContextMenuForContainer(Menu menu) { 187 MenuItem paste = menu.findItem(R.id.menu_paste_from_clipboard); 188 MenuItem selectAll = menu.findItem(R.id.menu_select_all); 189 MenuItem createDir = menu.findItem(R.id.menu_create_dir); 190 191 paste.setEnabled(mDirDetails.hasItemsToPaste() && mDirDetails.canCreateDoc()); 192 updateSelectAll(selectAll); 193 updateCreateDir(createDir); 194 } 195 196 /** 197 * @see RootsFragment#onCreateContextMenu 198 */ updateRootContextMenu(Menu menu, RootInfo root, DocumentInfo docInfo)199 public void updateRootContextMenu(Menu menu, RootInfo root, DocumentInfo docInfo) { 200 MenuItem eject = menu.findItem(R.id.menu_eject_root); 201 MenuItem pasteInto = menu.findItem(R.id.menu_paste_into_folder); 202 MenuItem openInNewWindow = menu.findItem(R.id.menu_open_in_new_window); 203 MenuItem settings = menu.findItem(R.id.menu_settings); 204 205 updateEject(eject, root); 206 updatePasteInto(pasteInto, root, docInfo); 207 updateOpenInNewWindow(openInNewWindow, root); 208 updateSettings(settings, root); 209 } 210 updateKeyboardShortcutsMenu( List<KeyboardShortcutGroup> data, IntFunction<String> stringSupplier)211 public abstract void updateKeyboardShortcutsMenu( 212 List<KeyboardShortcutGroup> data, IntFunction<String> stringSupplier); 213 updateModePicker(MenuItem grid, MenuItem list)214 protected void updateModePicker(MenuItem grid, MenuItem list) { 215 grid.setVisible(mState.derivedMode != State.MODE_GRID); 216 list.setVisible(mState.derivedMode != State.MODE_LIST); 217 } 218 updateAdvanced(MenuItem advanced)219 protected void updateAdvanced(MenuItem advanced) { 220 advanced.setVisible(mState.showDeviceStorageOption); 221 advanced.setTitle(mState.showDeviceStorageOption && mState.showAdvanced 222 ? R.string.menu_advanced_hide : R.string.menu_advanced_show); 223 } 224 updateDebug(MenuItem debug)225 protected void updateDebug(MenuItem debug) { 226 debug.setVisible(mState.debugMode); 227 } 228 updateSettings(MenuItem settings)229 protected void updateSettings(MenuItem settings) { 230 settings.setVisible(false); 231 } 232 updateSettings(MenuItem settings, RootInfo root)233 protected void updateSettings(MenuItem settings, RootInfo root) { 234 settings.setVisible(false); 235 } 236 updateEject(MenuItem eject, RootInfo root)237 protected void updateEject(MenuItem eject, RootInfo root) { 238 eject.setVisible(false); 239 } 240 updateNewWindow(MenuItem newWindow)241 protected void updateNewWindow(MenuItem newWindow) { 242 newWindow.setVisible(false); 243 } 244 updateOpenInActionMode(MenuItem open, SelectionDetails selectionDetails)245 protected void updateOpenInActionMode(MenuItem open, SelectionDetails selectionDetails) { 246 open.setVisible(false); 247 } 248 updateOpenWith(MenuItem openWith, SelectionDetails selectionDetails)249 protected void updateOpenWith(MenuItem openWith, SelectionDetails selectionDetails) { 250 openWith.setVisible(false); 251 } 252 updateOpenInNewWindow( MenuItem openInNewWindow, SelectionDetails selectionDetails)253 protected void updateOpenInNewWindow( 254 MenuItem openInNewWindow, SelectionDetails selectionDetails) { 255 openInNewWindow.setVisible(false); 256 } 257 updateOpenInNewWindow( MenuItem openInNewWindow, RootInfo root)258 protected void updateOpenInNewWindow( 259 MenuItem openInNewWindow, RootInfo root) { 260 openInNewWindow.setVisible(false); 261 } 262 updateShare(MenuItem share, SelectionDetails selectionDetails)263 protected void updateShare(MenuItem share, SelectionDetails selectionDetails) { 264 share.setVisible(false); 265 } 266 updateDelete(MenuItem delete, SelectionDetails selectionDetails)267 protected void updateDelete(MenuItem delete, SelectionDetails selectionDetails) { 268 delete.setVisible(false); 269 } 270 updateRename(MenuItem rename, SelectionDetails selectionDetails)271 protected void updateRename(MenuItem rename, SelectionDetails selectionDetails) { 272 rename.setVisible(false); 273 } 274 updateViewInOwner(MenuItem view, SelectionDetails selectionDetails)275 protected void updateViewInOwner(MenuItem view, SelectionDetails selectionDetails) { 276 view.setVisible(false); 277 } 278 updateMoveTo(MenuItem moveTo, SelectionDetails selectionDetails)279 protected void updateMoveTo(MenuItem moveTo, SelectionDetails selectionDetails) { 280 moveTo.setVisible(false); 281 } 282 updateCopyTo(MenuItem copyTo, SelectionDetails selectionDetails)283 protected void updateCopyTo(MenuItem copyTo, SelectionDetails selectionDetails) { 284 copyTo.setVisible(false); 285 } 286 updateCompress(MenuItem compress, SelectionDetails selectionDetails)287 protected void updateCompress(MenuItem compress, SelectionDetails selectionDetails) { 288 compress.setVisible(false); 289 } 290 updateExtractTo(MenuItem extractTo, SelectionDetails selectionDetails)291 protected void updateExtractTo(MenuItem extractTo, SelectionDetails selectionDetails) { 292 extractTo.setVisible(false); 293 } 294 updatePasteInto(MenuItem pasteInto, SelectionDetails selectionDetails)295 protected void updatePasteInto(MenuItem pasteInto, SelectionDetails selectionDetails) { 296 pasteInto.setVisible(false); 297 } 298 updatePasteInto(MenuItem pasteInto, RootInfo root, DocumentInfo docInfo)299 protected void updatePasteInto(MenuItem pasteInto, RootInfo root, DocumentInfo docInfo) { 300 pasteInto.setVisible(false); 301 } 302 updateOpenInContextMenu( MenuItem open, SelectionDetails selectionDetails)303 protected abstract void updateOpenInContextMenu( 304 MenuItem open, SelectionDetails selectionDetails); updateSelectAll(MenuItem selectAll)305 protected abstract void updateSelectAll(MenuItem selectAll); updateCreateDir(MenuItem createDir)306 protected abstract void updateCreateDir(MenuItem createDir); 307 308 /** 309 * Access to meta data about the selection. 310 */ 311 public interface SelectionDetails { containsDirectories()312 boolean containsDirectories(); 313 containsFiles()314 boolean containsFiles(); 315 size()316 int size(); 317 containsPartialFiles()318 boolean containsPartialFiles(); 319 320 // TODO: Update these to express characteristics instead of answering concrete questions, 321 // since the answer to those questions is (or can be) activity specific. canDelete()322 boolean canDelete(); 323 canRename()324 boolean canRename(); 325 canPasteInto()326 boolean canPasteInto(); 327 canExtract()328 boolean canExtract(); 329 canOpenWith()330 boolean canOpenWith(); 331 canViewInOwner()332 boolean canViewInOwner(); 333 } 334 335 public static class DirectoryDetails { 336 private final BaseActivity mActivity; 337 DirectoryDetails(BaseActivity activity)338 public DirectoryDetails(BaseActivity activity) { 339 mActivity = activity; 340 } 341 hasRootSettings()342 public boolean hasRootSettings() { 343 return mActivity.getCurrentRoot().hasSettings(); 344 } 345 hasItemsToPaste()346 public boolean hasItemsToPaste() { 347 return false; 348 } 349 canCreateDoc()350 public boolean canCreateDoc() { 351 return isInRecents() ? false : mActivity.getCurrentDirectory().isCreateSupported(); 352 } 353 isInRecents()354 public boolean isInRecents() { 355 return mActivity.getCurrentDirectory() == null; 356 } 357 canCreateDirectory()358 public boolean canCreateDirectory() { 359 return mActivity.canCreateDirectory(); 360 } 361 } 362 } 363