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