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