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.app.Fragment;
20 import android.view.KeyboardShortcutGroup;
21 import android.view.Menu;
22 import android.view.MenuInflater;
23 import android.view.MenuItem;
24 import android.view.View;
25 
26 import com.android.documentsui.base.DocumentInfo;
27 import com.android.documentsui.base.Menus;
28 import com.android.documentsui.base.RootInfo;
29 import com.android.documentsui.base.State;
30 import com.android.documentsui.dirlist.DirectoryFragment;
31 import com.android.documentsui.files.FilesActivity;
32 import com.android.documentsui.queries.SearchViewManager;
33 import com.android.documentsui.sidebar.RootsFragment;
34 import com.android.internal.annotations.VisibleForTesting;
35 
36 import java.util.List;
37 import java.util.function.IntFunction;
38 
39 public abstract class MenuManager {
40 
41     final protected SearchViewManager mSearchManager;
42     final protected State mState;
43     final protected DirectoryDetails mDirDetails;
44 
MenuManager( SearchViewManager searchManager, State displayState, DirectoryDetails dirDetails)45     public MenuManager(
46             SearchViewManager searchManager,
47             State displayState,
48             DirectoryDetails dirDetails) {
49         mSearchManager = searchManager;
50         mState = displayState;
51         mDirDetails = dirDetails;
52     }
53 
54     /** @see ActionModeController */
updateActionMenu(Menu menu, SelectionDetails selection)55     public void updateActionMenu(Menu menu, SelectionDetails selection) {
56         updateOpenInActionMode(menu.findItem(R.id.menu_open), selection);
57         updateOpenWith(menu.findItem(R.id.menu_open_with), selection);
58         updateDelete(menu.findItem(R.id.menu_delete), selection);
59         updateShare(menu.findItem(R.id.menu_share), selection);
60         updateRename(menu.findItem(R.id.menu_rename), selection);
61         updateSelectAll(menu.findItem(R.id.menu_select_all));
62         updateMoveTo(menu.findItem(R.id.menu_move_to), selection);
63         updateCopyTo(menu.findItem(R.id.menu_copy_to), selection);
64         updateCompress(menu.findItem(R.id.menu_compress), selection);
65         updateExtractTo(menu.findItem(R.id.menu_extract_to), selection);
66         updateViewInOwner(menu.findItem(R.id.menu_view_in_owner), selection);
67 
68         Menus.disableHiddenItems(menu);
69     }
70 
71     /** @see BaseActivity#onPrepareOptionsMenu */
updateOptionMenu(Menu menu)72     public void updateOptionMenu(Menu menu) {
73         updateCreateDir(menu.findItem(R.id.menu_create_dir));
74         updateSettings(menu.findItem(R.id.menu_settings));
75         updateSelectAll(menu.findItem(R.id.menu_select_all));
76         updateNewWindow(menu.findItem(R.id.menu_new_window));
77         updateModePicker(menu.findItem(R.id.menu_grid), menu.findItem(R.id.menu_list));
78         updateAdvanced(menu.findItem(R.id.menu_advanced));
79         updateDebug(menu.findItem(R.id.menu_debug));
80 
81         Menus.disableHiddenItems(menu);
82     }
83 
84     /**
85      * Called when we needs {@link MenuManager} to ask Android to show context menu for us.
86      * {@link MenuManager} can choose to defeat this request.
87      *
88      * {@link #inflateContextMenuForDocs} and {@link #inflateContextMenuForContainer} are called
89      * afterwards when Android asks us to provide the content of context menus, so they're not
90      * correct locations to suppress context menus.
91      */
showContextMenu(Fragment f, View v, float x, float y)92     public void showContextMenu(Fragment f, View v, float x, float y) {
93         // Pickers don't have any context menu at this moment.
94     }
95 
inflateContextMenuForContainer(Menu menu, MenuInflater inflater)96     public void inflateContextMenuForContainer(Menu menu, MenuInflater inflater) {
97         throw new UnsupportedOperationException("Pickers don't allow context menu.");
98     }
99 
inflateContextMenuForDocs( Menu menu, MenuInflater inflater, SelectionDetails selectionDetails)100     public void inflateContextMenuForDocs(
101             Menu menu, MenuInflater inflater, SelectionDetails selectionDetails) {
102         throw new UnsupportedOperationException("Pickers don't allow context menu.");
103     }
104 
105     /**
106      * @see DirectoryFragment#onCreateContextMenu
107      *
108      * Called when user tries to generate a context menu anchored to a file when the selection
109      * doesn't contain any folder.
110      *
111      * @param selectionDetails
112      *      containsFiles may return false because this may be called when user right clicks on an
113      *      unselectable item in pickers
114      */
115     @VisibleForTesting
updateContextMenuForFiles(Menu menu, SelectionDetails selectionDetails)116     public void updateContextMenuForFiles(Menu menu, SelectionDetails selectionDetails) {
117         assert(selectionDetails != null);
118 
119         MenuItem share = menu.findItem(R.id.menu_share);
120         MenuItem open = menu.findItem(R.id.menu_open);
121         MenuItem openWith = menu.findItem(R.id.menu_open_with);
122         MenuItem rename = menu.findItem(R.id.menu_rename);
123         MenuItem viewInOwner = menu.findItem(R.id.menu_view_in_owner);
124 
125         updateShare(share, selectionDetails);
126         updateOpenInContextMenu(open, selectionDetails);
127         updateOpenWith(openWith, selectionDetails);
128         updateRename(rename, selectionDetails);
129         updateViewInOwner(viewInOwner, selectionDetails);
130 
131         updateContextMenu(menu, selectionDetails);
132     }
133 
134     /**
135      * @see DirectoryFragment#onCreateContextMenu
136      *
137      * Called when user tries to generate a context menu anchored to a folder when the selection
138      * doesn't contain any file.
139      *
140      * @param selectionDetails
141      *      containDirectories may return false because this may be called when user right clicks on
142      *      an unselectable item in pickers
143      */
144     @VisibleForTesting
updateContextMenuForDirs(Menu menu, SelectionDetails selectionDetails)145     public void updateContextMenuForDirs(Menu menu, SelectionDetails selectionDetails) {
146         assert(selectionDetails != null);
147 
148         MenuItem openInNewWindow = menu.findItem(R.id.menu_open_in_new_window);
149         MenuItem rename = menu.findItem(R.id.menu_rename);
150         MenuItem pasteInto = menu.findItem(R.id.menu_paste_into_folder);
151 
152         updateOpenInNewWindow(openInNewWindow, selectionDetails);
153         updateRename(rename, selectionDetails);
154         updatePasteInto(pasteInto, selectionDetails);
155 
156         updateContextMenu(menu, selectionDetails);
157     }
158 
159     /**
160      * @see DirectoryFragment#onCreateContextMenu
161      *
162      * Update shared context menu items of both files and folders context menus.
163      */
164     @VisibleForTesting
updateContextMenu(Menu menu, SelectionDetails selectionDetails)165     public void updateContextMenu(Menu menu, SelectionDetails selectionDetails) {
166         assert(selectionDetails != null);
167 
168         MenuItem cut = menu.findItem(R.id.menu_cut_to_clipboard);
169         MenuItem copy = menu.findItem(R.id.menu_copy_to_clipboard);
170         MenuItem delete = menu.findItem(R.id.menu_delete);
171 
172         final boolean canCopy =
173                 selectionDetails.size() > 0 && !selectionDetails.containsPartialFiles();
174         final boolean canDelete = selectionDetails.canDelete();
175         cut.setEnabled(canCopy && canDelete);
176         copy.setEnabled(canCopy);
177         delete.setEnabled(canDelete);
178     }
179 
180     /**
181      * @see DirectoryFragment#onCreateContextMenu
182      *
183      * Called when user tries to generate a context menu anchored to an empty pane.
184      */
185     @VisibleForTesting
updateContextMenuForContainer(Menu menu)186     public void updateContextMenuForContainer(Menu menu) {
187         MenuItem paste = menu.findItem(R.id.menu_paste_from_clipboard);
188         MenuItem selectAll = menu.findItem(R.id.menu_select_all);
189         MenuItem createDir = menu.findItem(R.id.menu_create_dir);
190 
191         paste.setEnabled(mDirDetails.hasItemsToPaste() && mDirDetails.canCreateDoc());
192         updateSelectAll(selectAll);
193         updateCreateDir(createDir);
194     }
195 
196     /**
197      * @see RootsFragment#onCreateContextMenu
198      */
updateRootContextMenu(Menu menu, RootInfo root, DocumentInfo docInfo)199     public void updateRootContextMenu(Menu menu, RootInfo root, DocumentInfo docInfo) {
200         MenuItem eject = menu.findItem(R.id.menu_eject_root);
201         MenuItem pasteInto = menu.findItem(R.id.menu_paste_into_folder);
202         MenuItem openInNewWindow = menu.findItem(R.id.menu_open_in_new_window);
203         MenuItem settings = menu.findItem(R.id.menu_settings);
204 
205         updateEject(eject, root);
206         updatePasteInto(pasteInto, root, docInfo);
207         updateOpenInNewWindow(openInNewWindow, root);
208         updateSettings(settings, root);
209     }
210 
updateKeyboardShortcutsMenu( List<KeyboardShortcutGroup> data, IntFunction<String> stringSupplier)211     public abstract void updateKeyboardShortcutsMenu(
212             List<KeyboardShortcutGroup> data, IntFunction<String> stringSupplier);
213 
updateModePicker(MenuItem grid, MenuItem list)214     protected void updateModePicker(MenuItem grid, MenuItem list) {
215         grid.setVisible(mState.derivedMode != State.MODE_GRID);
216         list.setVisible(mState.derivedMode != State.MODE_LIST);
217     }
218 
updateAdvanced(MenuItem advanced)219     protected void updateAdvanced(MenuItem advanced) {
220         advanced.setVisible(mState.showDeviceStorageOption);
221         advanced.setTitle(mState.showDeviceStorageOption && mState.showAdvanced
222                 ? R.string.menu_advanced_hide : R.string.menu_advanced_show);
223     }
224 
updateDebug(MenuItem debug)225     protected void updateDebug(MenuItem debug) {
226         debug.setVisible(mState.debugMode);
227     }
228 
updateSettings(MenuItem settings)229     protected void updateSettings(MenuItem settings) {
230         settings.setVisible(false);
231     }
232 
updateSettings(MenuItem settings, RootInfo root)233     protected void updateSettings(MenuItem settings, RootInfo root) {
234         settings.setVisible(false);
235     }
236 
updateEject(MenuItem eject, RootInfo root)237     protected void updateEject(MenuItem eject, RootInfo root) {
238         eject.setVisible(false);
239     }
240 
updateNewWindow(MenuItem newWindow)241     protected void updateNewWindow(MenuItem newWindow) {
242         newWindow.setVisible(false);
243     }
244 
updateOpenInActionMode(MenuItem open, SelectionDetails selectionDetails)245     protected void updateOpenInActionMode(MenuItem open, SelectionDetails selectionDetails) {
246         open.setVisible(false);
247     }
248 
updateOpenWith(MenuItem openWith, SelectionDetails selectionDetails)249     protected void updateOpenWith(MenuItem openWith, SelectionDetails selectionDetails) {
250         openWith.setVisible(false);
251     }
252 
updateOpenInNewWindow( MenuItem openInNewWindow, SelectionDetails selectionDetails)253     protected void updateOpenInNewWindow(
254             MenuItem openInNewWindow, SelectionDetails selectionDetails) {
255         openInNewWindow.setVisible(false);
256     }
257 
updateOpenInNewWindow( MenuItem openInNewWindow, RootInfo root)258     protected void updateOpenInNewWindow(
259             MenuItem openInNewWindow, RootInfo root) {
260         openInNewWindow.setVisible(false);
261     }
262 
updateShare(MenuItem share, SelectionDetails selectionDetails)263     protected void updateShare(MenuItem share, SelectionDetails selectionDetails) {
264         share.setVisible(false);
265     }
266 
updateDelete(MenuItem delete, SelectionDetails selectionDetails)267     protected void updateDelete(MenuItem delete, SelectionDetails selectionDetails) {
268         delete.setVisible(false);
269     }
270 
updateRename(MenuItem rename, SelectionDetails selectionDetails)271     protected void updateRename(MenuItem rename, SelectionDetails selectionDetails) {
272         rename.setVisible(false);
273     }
274 
updateViewInOwner(MenuItem view, SelectionDetails selectionDetails)275     protected void updateViewInOwner(MenuItem view, SelectionDetails selectionDetails) {
276         view.setVisible(false);
277     }
278 
updateMoveTo(MenuItem moveTo, SelectionDetails selectionDetails)279     protected void updateMoveTo(MenuItem moveTo, SelectionDetails selectionDetails) {
280         moveTo.setVisible(false);
281     }
282 
updateCopyTo(MenuItem copyTo, SelectionDetails selectionDetails)283     protected void updateCopyTo(MenuItem copyTo, SelectionDetails selectionDetails) {
284         copyTo.setVisible(false);
285     }
286 
updateCompress(MenuItem compress, SelectionDetails selectionDetails)287     protected void updateCompress(MenuItem compress, SelectionDetails selectionDetails) {
288         compress.setVisible(false);
289     }
290 
updateExtractTo(MenuItem extractTo, SelectionDetails selectionDetails)291     protected void updateExtractTo(MenuItem extractTo, SelectionDetails selectionDetails) {
292         extractTo.setVisible(false);
293     }
294 
updatePasteInto(MenuItem pasteInto, SelectionDetails selectionDetails)295     protected void updatePasteInto(MenuItem pasteInto, SelectionDetails selectionDetails) {
296         pasteInto.setVisible(false);
297     }
298 
updatePasteInto(MenuItem pasteInto, RootInfo root, DocumentInfo docInfo)299     protected void updatePasteInto(MenuItem pasteInto, RootInfo root, DocumentInfo docInfo) {
300         pasteInto.setVisible(false);
301     }
302 
updateOpenInContextMenu( MenuItem open, SelectionDetails selectionDetails)303     protected abstract void updateOpenInContextMenu(
304             MenuItem open, SelectionDetails selectionDetails);
updateSelectAll(MenuItem selectAll)305     protected abstract void updateSelectAll(MenuItem selectAll);
updateCreateDir(MenuItem createDir)306     protected abstract void updateCreateDir(MenuItem createDir);
307 
308     /**
309      * Access to meta data about the selection.
310      */
311     public interface SelectionDetails {
containsDirectories()312         boolean containsDirectories();
313 
containsFiles()314         boolean containsFiles();
315 
size()316         int size();
317 
containsPartialFiles()318         boolean containsPartialFiles();
319 
320         // TODO: Update these to express characteristics instead of answering concrete questions,
321         // since the answer to those questions is (or can be) activity specific.
canDelete()322         boolean canDelete();
323 
canRename()324         boolean canRename();
325 
canPasteInto()326         boolean canPasteInto();
327 
canExtract()328         boolean canExtract();
329 
canOpenWith()330         boolean canOpenWith();
331 
canViewInOwner()332         boolean canViewInOwner();
333     }
334 
335     public static class DirectoryDetails {
336         private final BaseActivity mActivity;
337 
DirectoryDetails(BaseActivity activity)338         public DirectoryDetails(BaseActivity activity) {
339             mActivity = activity;
340         }
341 
hasRootSettings()342         public boolean hasRootSettings() {
343             return mActivity.getCurrentRoot().hasSettings();
344         }
345 
hasItemsToPaste()346         public boolean hasItemsToPaste() {
347             return false;
348         }
349 
canCreateDoc()350         public boolean canCreateDoc() {
351             return isInRecents() ? false : mActivity.getCurrentDirectory().isCreateSupported();
352         }
353 
isInRecents()354         public boolean isInRecents() {
355             return mActivity.getCurrentDirectory() == null;
356         }
357 
canCreateDirectory()358         public boolean canCreateDirectory() {
359             return mActivity.canCreateDirectory();
360         }
361     }
362 }
363