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.view.KeyboardShortcutGroup; 20 import android.view.Menu; 21 import android.view.MenuInflater; 22 import android.view.MenuItem; 23 import android.view.View; 24 25 import androidx.annotation.VisibleForTesting; 26 import androidx.fragment.app.Fragment; 27 28 import com.android.documentsui.base.DocumentInfo; 29 import com.android.documentsui.base.Menus; 30 import com.android.documentsui.base.RootInfo; 31 import com.android.documentsui.base.State; 32 import com.android.documentsui.dirlist.DirectoryFragment; 33 import com.android.documentsui.queries.SearchViewManager; 34 import com.android.documentsui.sidebar.RootsFragment; 35 36 import java.util.List; 37 import java.util.function.IntFunction; 38 import java.util.function.IntSupplier; 39 40 public abstract class MenuManager { 41 private final static String TAG = "MenuManager"; 42 43 protected final SearchViewManager mSearchManager; 44 protected final State mState; 45 protected final DirectoryDetails mDirDetails; 46 protected final IntSupplier mFilesCountSupplier; 47 48 protected Menu mOptionMenu; 49 MenuManager( SearchViewManager searchManager, State displayState, DirectoryDetails dirDetails, IntSupplier filesCountSupplier)50 public MenuManager( 51 SearchViewManager searchManager, 52 State displayState, 53 DirectoryDetails dirDetails, 54 IntSupplier filesCountSupplier) { 55 mSearchManager = searchManager; 56 mState = displayState; 57 mDirDetails = dirDetails; 58 mFilesCountSupplier = filesCountSupplier; 59 } 60 61 /** @see ActionModeController */ updateActionMenu(Menu menu, SelectionDetails selection)62 public void updateActionMenu(Menu menu, SelectionDetails selection) { 63 updateOpenWith(menu.findItem(R.id.action_menu_open_with), selection); 64 updateDelete(menu.findItem(R.id.action_menu_delete), selection); 65 updateShare(menu.findItem(R.id.action_menu_share), selection); 66 updateRename(menu.findItem(R.id.action_menu_rename), selection); 67 updateSelect(menu.findItem(R.id.action_menu_select), selection); 68 updateSelectAll(menu.findItem(R.id.action_menu_select_all), selection); 69 updateDeselectAll(menu.findItem(R.id.action_menu_deselect_all), selection); 70 updateMoveTo(menu.findItem(R.id.action_menu_move_to), selection); 71 updateCopyTo(menu.findItem(R.id.action_menu_copy_to), selection); 72 updateCompress(menu.findItem(R.id.action_menu_compress), selection); 73 updateExtractTo(menu.findItem(R.id.action_menu_extract_to), selection); 74 updateInspect(menu.findItem(R.id.action_menu_inspect), selection); 75 updateViewInOwner(menu.findItem(R.id.action_menu_view_in_owner), selection); 76 updateSort(menu.findItem(R.id.action_menu_sort)); 77 78 Menus.disableHiddenItems(menu); 79 } 80 81 /** @see BaseActivity#onPrepareOptionsMenu */ updateOptionMenu(Menu menu)82 public void updateOptionMenu(Menu menu) { 83 mOptionMenu = menu; 84 updateOptionMenu(); 85 } 86 updateOptionMenu()87 public void updateOptionMenu() { 88 if (mOptionMenu == null) { 89 return; 90 } 91 updateCreateDir(mOptionMenu.findItem(R.id.option_menu_create_dir)); 92 updateSettings(mOptionMenu.findItem(R.id.option_menu_settings)); 93 updateSelectAll(mOptionMenu.findItem(R.id.option_menu_select_all)); 94 updateNewWindow(mOptionMenu.findItem(R.id.option_menu_new_window)); 95 updateDebug(mOptionMenu.findItem(R.id.option_menu_debug)); 96 updateInspect(mOptionMenu.findItem(R.id.option_menu_inspect)); 97 updateSort(mOptionMenu.findItem(R.id.option_menu_sort)); 98 updateLauncher(mOptionMenu.findItem(R.id.option_menu_launcher)); 99 updateShowHiddenFiles(mOptionMenu.findItem(R.id.option_menu_show_hidden_files)); 100 101 Menus.disableHiddenItems(mOptionMenu); 102 mSearchManager.updateMenu(); 103 } 104 updateSubMenu(Menu menu)105 public void updateSubMenu(Menu menu) { 106 updateModePicker(menu.findItem(R.id.sub_menu_grid), menu.findItem(R.id.sub_menu_list)); 107 } 108 updateModel(Model model)109 public void updateModel(Model model) {} 110 111 /** 112 * Called when we needs {@link MenuManager} to ask Android to show context menu for us. 113 * {@link MenuManager} can choose to defeat this request. 114 * 115 * {@link #inflateContextMenuForDocs} and {@link #inflateContextMenuForContainer} are called 116 * afterwards when Android asks us to provide the content of context menus, so they're not 117 * correct locations to suppress context menus. 118 */ showContextMenu(Fragment f, View v, float x, float y)119 public void showContextMenu(Fragment f, View v, float x, float y) { 120 // Pickers don't have any context menu at this moment. 121 } 122 123 /** 124 * Called when container context menu needs to be inflated. 125 * 126 * @param menu context menu from activity or fragment 127 * @param inflater the MenuInflater 128 * @param selectionDetails selection of files 129 */ inflateContextMenuForContainer( Menu menu, MenuInflater inflater, SelectionDetails selectionDetails)130 public void inflateContextMenuForContainer( 131 Menu menu, MenuInflater inflater, SelectionDetails selectionDetails) { 132 throw new UnsupportedOperationException("Pickers don't allow context menu."); 133 } 134 inflateContextMenuForDocs( Menu menu, MenuInflater inflater, SelectionDetails selectionDetails)135 public void inflateContextMenuForDocs( 136 Menu menu, MenuInflater inflater, SelectionDetails selectionDetails) { 137 throw new UnsupportedOperationException("Pickers don't allow context menu."); 138 } 139 140 /** 141 * @see DirectoryFragment#onCreateContextMenu 142 * 143 * Called when user tries to generate a context menu anchored to a file when the selection 144 * doesn't contain any folder. 145 * 146 * @param selectionDetails 147 * containsFiles may return false because this may be called when user right clicks on an 148 * unselectable item in pickers 149 */ 150 @VisibleForTesting updateContextMenuForFiles(Menu menu, SelectionDetails selectionDetails)151 public void updateContextMenuForFiles(Menu menu, SelectionDetails selectionDetails) { 152 assert selectionDetails != null; 153 154 MenuItem share = menu.findItem(R.id.dir_menu_share); 155 MenuItem open = menu.findItem(R.id.dir_menu_open); 156 MenuItem openWith = menu.findItem(R.id.dir_menu_open_with); 157 MenuItem rename = menu.findItem(R.id.dir_menu_rename); 158 MenuItem viewInOwner = menu.findItem(R.id.dir_menu_view_in_owner); 159 160 updateShare(share, selectionDetails); 161 updateOpenInContextMenu(open, selectionDetails); 162 updateOpenWith(openWith, selectionDetails); 163 updateRename(rename, selectionDetails); 164 updateViewInOwner(viewInOwner, selectionDetails); 165 166 updateContextMenu(menu, selectionDetails); 167 } 168 169 /** 170 * @see DirectoryFragment#onCreateContextMenu 171 * 172 * Called when user tries to generate a context menu anchored to a folder when the selection 173 * doesn't contain any file. 174 * 175 * @param selectionDetails 176 * containDirectories may return false because this may be called when user right clicks on 177 * an unselectable item in pickers 178 */ 179 @VisibleForTesting updateContextMenuForDirs(Menu menu, SelectionDetails selectionDetails)180 public void updateContextMenuForDirs(Menu menu, SelectionDetails selectionDetails) { 181 assert selectionDetails != null; 182 183 MenuItem openInNewWindow = menu.findItem(R.id.dir_menu_open_in_new_window); 184 MenuItem rename = menu.findItem(R.id.dir_menu_rename); 185 MenuItem pasteInto = menu.findItem(R.id.dir_menu_paste_into_folder); 186 187 updateOpenInNewWindow(openInNewWindow, selectionDetails); 188 updateRename(rename, selectionDetails); 189 updatePasteInto(pasteInto, selectionDetails); 190 191 updateContextMenu(menu, selectionDetails); 192 } 193 194 /** 195 * @see DirectoryFragment#onCreateContextMenu 196 * 197 * Update shared context menu items of both files and folders context menus. 198 */ 199 @VisibleForTesting updateContextMenu(Menu menu, SelectionDetails selectionDetails)200 public void updateContextMenu(Menu menu, SelectionDetails selectionDetails) { 201 assert selectionDetails != null; 202 203 MenuItem cut = menu.findItem(R.id.dir_menu_cut_to_clipboard); 204 MenuItem copy = menu.findItem(R.id.dir_menu_copy_to_clipboard); 205 MenuItem delete = menu.findItem(R.id.dir_menu_delete); 206 MenuItem inspect = menu.findItem(R.id.dir_menu_inspect); 207 208 final boolean canCopy = 209 selectionDetails.size() > 0 && !selectionDetails.containsPartialFiles(); 210 final boolean canDelete = selectionDetails.canDelete(); 211 Menus.setEnabledAndVisible(cut, canCopy && canDelete); 212 Menus.setEnabledAndVisible(copy, canCopy); 213 Menus.setEnabledAndVisible(delete, canDelete); 214 215 Menus.setEnabledAndVisible(inspect, selectionDetails.size() == 1); 216 } 217 218 /** 219 * @see DirectoryFragment#onCreateContextMenu 220 * 221 * Called when user tries to generate a context menu anchored to an empty pane. 222 */ 223 @VisibleForTesting updateContextMenuForContainer(Menu menu, SelectionDetails selectionDetails)224 public void updateContextMenuForContainer(Menu menu, SelectionDetails selectionDetails) { 225 MenuItem paste = menu.findItem(R.id.dir_menu_paste_from_clipboard); 226 MenuItem selectAll = menu.findItem(R.id.dir_menu_select_all); 227 MenuItem deselectAll = menu.findItem(R.id.dir_menu_deselect_all); 228 MenuItem createDir = menu.findItem(R.id.dir_menu_create_dir); 229 MenuItem inspect = menu.findItem(R.id.dir_menu_inspect); 230 231 Menus.setEnabledAndVisible(paste, 232 mDirDetails.hasItemsToPaste() && mDirDetails.canCreateDoc()); 233 updateSelectAll(selectAll, selectionDetails); 234 updateDeselectAll(deselectAll, selectionDetails); 235 updateCreateDir(createDir); 236 updateInspect(inspect); 237 } 238 239 /** 240 * @see RootsFragment#onCreateContextMenu 241 */ updateRootContextMenu(Menu menu, RootInfo root, DocumentInfo docInfo)242 public void updateRootContextMenu(Menu menu, RootInfo root, DocumentInfo docInfo) { 243 MenuItem eject = menu.findItem(R.id.root_menu_eject_root); 244 MenuItem pasteInto = menu.findItem(R.id.root_menu_paste_into_folder); 245 MenuItem openInNewWindow = menu.findItem(R.id.root_menu_open_in_new_window); 246 MenuItem settings = menu.findItem(R.id.root_menu_settings); 247 248 updateEject(eject, root); 249 updatePasteInto(pasteInto, root, docInfo); 250 updateOpenInNewWindow(openInNewWindow, root); 251 updateSettings(settings, root); 252 } 253 updateKeyboardShortcutsMenu( List<KeyboardShortcutGroup> data, IntFunction<String> stringSupplier)254 public abstract void updateKeyboardShortcutsMenu( 255 List<KeyboardShortcutGroup> data, IntFunction<String> stringSupplier); 256 updateModePicker(MenuItem grid, MenuItem list)257 protected void updateModePicker(MenuItem grid, MenuItem list) { 258 // The order of enabling disabling menu item in wrong order removed accessibility focus. 259 if (mState.derivedMode != State.MODE_LIST) { 260 Menus.setEnabledAndVisible(list, mState.derivedMode != State.MODE_LIST); 261 Menus.setEnabledAndVisible(grid, mState.derivedMode != State.MODE_GRID); 262 } else { 263 Menus.setEnabledAndVisible(grid, mState.derivedMode != State.MODE_GRID); 264 Menus.setEnabledAndVisible(list, mState.derivedMode != State.MODE_LIST); 265 } 266 } 267 updateShowHiddenFiles(MenuItem showHidden)268 protected void updateShowHiddenFiles(MenuItem showHidden) { 269 Menus.setEnabledAndVisible(showHidden, true); 270 showHidden.setTitle(mState.showHiddenFiles 271 ? R.string.menu_hide_hidden_files 272 : R.string.menu_show_hidden_files); 273 } 274 updateSort(MenuItem sort)275 protected void updateSort(MenuItem sort) { 276 Menus.setEnabledAndVisible(sort, true); 277 } 278 updateDebug(MenuItem debug)279 protected void updateDebug(MenuItem debug) { 280 Menus.setEnabledAndVisible(debug, mState.debugMode); 281 } 282 updateSettings(MenuItem settings)283 protected void updateSettings(MenuItem settings) { 284 Menus.setEnabledAndVisible(settings, false); 285 } 286 updateSettings(MenuItem settings, RootInfo root)287 protected void updateSettings(MenuItem settings, RootInfo root) { 288 Menus.setEnabledAndVisible(settings, false); 289 } 290 updateEject(MenuItem eject, RootInfo root)291 protected void updateEject(MenuItem eject, RootInfo root) { 292 Menus.setEnabledAndVisible(eject, false); 293 } 294 updateNewWindow(MenuItem newWindow)295 protected void updateNewWindow(MenuItem newWindow) { 296 Menus.setEnabledAndVisible(newWindow, false); 297 } 298 updateSelect(MenuItem select, SelectionDetails selectionDetails)299 protected void updateSelect(MenuItem select, SelectionDetails selectionDetails) { 300 Menus.setEnabledAndVisible(select, false); 301 } 302 updateOpenWith(MenuItem openWith, SelectionDetails selectionDetails)303 protected void updateOpenWith(MenuItem openWith, SelectionDetails selectionDetails) { 304 Menus.setEnabledAndVisible(openWith, false); 305 } 306 updateOpenInNewWindow( MenuItem openInNewWindow, SelectionDetails selectionDetails)307 protected void updateOpenInNewWindow( 308 MenuItem openInNewWindow, SelectionDetails selectionDetails) { 309 Menus.setEnabledAndVisible(openInNewWindow, false); 310 } 311 updateOpenInNewWindow( MenuItem openInNewWindow, RootInfo root)312 protected void updateOpenInNewWindow( 313 MenuItem openInNewWindow, RootInfo root) { 314 Menus.setEnabledAndVisible(openInNewWindow, false); 315 } 316 updateShare(MenuItem share, SelectionDetails selectionDetails)317 protected void updateShare(MenuItem share, SelectionDetails selectionDetails) { 318 Menus.setEnabledAndVisible(share, false); 319 } 320 updateDelete(MenuItem delete, SelectionDetails selectionDetails)321 protected void updateDelete(MenuItem delete, SelectionDetails selectionDetails) { 322 Menus.setEnabledAndVisible(delete, false); 323 } 324 updateRename(MenuItem rename, SelectionDetails selectionDetails)325 protected void updateRename(MenuItem rename, SelectionDetails selectionDetails) { 326 Menus.setEnabledAndVisible(rename, false); 327 } 328 329 /** 330 * This method is called for standard activity option menu as opposed 331 * to when there is a selection. 332 */ updateInspect(MenuItem inspector)333 protected void updateInspect(MenuItem inspector) { 334 Menus.setEnabledAndVisible(inspector, false); 335 } 336 337 /** 338 * This method is called for action mode, when a selection exists. 339 */ updateInspect(MenuItem inspect, SelectionDetails selectionDetails)340 protected void updateInspect(MenuItem inspect, SelectionDetails selectionDetails) { 341 Menus.setEnabledAndVisible(inspect, false); 342 } 343 updateViewInOwner(MenuItem view, SelectionDetails selectionDetails)344 protected void updateViewInOwner(MenuItem view, SelectionDetails selectionDetails) { 345 Menus.setEnabledAndVisible(view, false); 346 } 347 updateMoveTo(MenuItem moveTo, SelectionDetails selectionDetails)348 protected void updateMoveTo(MenuItem moveTo, SelectionDetails selectionDetails) { 349 Menus.setEnabledAndVisible(moveTo, false); 350 } 351 updateCopyTo(MenuItem copyTo, SelectionDetails selectionDetails)352 protected void updateCopyTo(MenuItem copyTo, SelectionDetails selectionDetails) { 353 Menus.setEnabledAndVisible(copyTo, false); 354 } 355 updateCompress(MenuItem compress, SelectionDetails selectionDetails)356 protected void updateCompress(MenuItem compress, SelectionDetails selectionDetails) { 357 Menus.setEnabledAndVisible(compress, false); 358 } 359 updateExtractTo(MenuItem extractTo, SelectionDetails selectionDetails)360 protected void updateExtractTo(MenuItem extractTo, SelectionDetails selectionDetails) { 361 Menus.setEnabledAndVisible(extractTo, false); 362 } 363 updatePasteInto(MenuItem pasteInto, SelectionDetails selectionDetails)364 protected void updatePasteInto(MenuItem pasteInto, SelectionDetails selectionDetails) { 365 Menus.setEnabledAndVisible(pasteInto, false); 366 } 367 updatePasteInto(MenuItem pasteInto, RootInfo root, DocumentInfo docInfo)368 protected void updatePasteInto(MenuItem pasteInto, RootInfo root, DocumentInfo docInfo) { 369 Menus.setEnabledAndVisible(pasteInto, false); 370 } 371 updateOpenInContextMenu(MenuItem open, SelectionDetails selectionDetails)372 protected void updateOpenInContextMenu(MenuItem open, SelectionDetails selectionDetails) { 373 Menus.setEnabledAndVisible(open, false); 374 } 375 updateLauncher(MenuItem launcher)376 protected void updateLauncher(MenuItem launcher) { 377 Menus.setEnabledAndVisible(launcher, false); 378 } 379 updateSelectAll(MenuItem selectAll)380 protected abstract void updateSelectAll(MenuItem selectAll); updateSelectAll(MenuItem selectAll, SelectionDetails selectionDetails)381 protected abstract void updateSelectAll(MenuItem selectAll, SelectionDetails selectionDetails); updateDeselectAll( MenuItem deselectAll, SelectionDetails selectionDetails)382 protected abstract void updateDeselectAll( 383 MenuItem deselectAll, SelectionDetails selectionDetails); updateCreateDir(MenuItem createDir)384 protected abstract void updateCreateDir(MenuItem createDir); 385 386 /** 387 * Access to meta data about the selection. 388 */ 389 public interface SelectionDetails { containsDirectories()390 boolean containsDirectories(); 391 containsFiles()392 boolean containsFiles(); 393 size()394 int size(); 395 containsPartialFiles()396 boolean containsPartialFiles(); 397 containsFilesInArchive()398 boolean containsFilesInArchive(); 399 400 // TODO: Update these to express characteristics instead of answering concrete questions, 401 // since the answer to those questions is (or can be) activity specific. canDelete()402 boolean canDelete(); 403 canRename()404 boolean canRename(); 405 canPasteInto()406 boolean canPasteInto(); 407 canExtract()408 boolean canExtract(); 409 canOpenWith()410 boolean canOpenWith(); 411 canViewInOwner()412 boolean canViewInOwner(); 413 } 414 415 public static class DirectoryDetails { 416 private final BaseActivity mActivity; 417 DirectoryDetails(BaseActivity activity)418 public DirectoryDetails(BaseActivity activity) { 419 mActivity = activity; 420 } 421 hasRootSettings()422 public boolean hasRootSettings() { 423 return mActivity.getCurrentRoot().hasSettings(); 424 } 425 hasItemsToPaste()426 public boolean hasItemsToPaste() { 427 return false; 428 } 429 canCreateDoc()430 public boolean canCreateDoc() { 431 return isInRecents() ? false : mActivity.getCurrentDirectory().isCreateSupported(); 432 } 433 isInRecents()434 public boolean isInRecents() { 435 return mActivity.isInRecents(); 436 } 437 canCreateDirectory()438 public boolean canCreateDirectory() { 439 return mActivity.canCreateDirectory(); 440 } 441 canInspectDirectory()442 public boolean canInspectDirectory() { 443 return mActivity.canInspectDirectory() && !isInRecents(); 444 } 445 } 446 } 447