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