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