2 * Copyright (C) 2015 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com
.android
.documentsui
.dirlist
;
19 import static com
.android
.documentsui
.State
.ACTION_BROWSE
;
20 import static com
.android
.documentsui
.State
.ACTION_CREATE
;
21 import static com
.android
.documentsui
.State
.ACTION_GET_CONTENT
;
22 import static com
.android
.documentsui
.State
.ACTION_OPEN
;
23 import static com
.android
.documentsui
.State
.ACTION_OPEN_TREE
;
24 import static com
.android
.documentsui
.State
.ACTION_PICK_COPY_DESTINATION
;
26 import android
.content
.Context
;
27 import android
.provider
.DocumentsContract
.Document
;
28 import android
.view
.Menu
;
29 import android
.view
.MenuItem
;
31 import com
.android
.documentsui
.BaseActivity
;
32 import com
.android
.documentsui
.Menus
;
33 import com
.android
.documentsui
.MimePredicate
;
34 import com
.android
.documentsui
.R
;
35 import com
.android
.documentsui
.State
;
36 import com
.android
.documentsui
.dirlist
.DirectoryFragment
.ResultType
;
39 * Providers support for specializing the DirectoryFragment to the "host" Activity.
40 * Feel free to expand the role of this class to handle other specializations.
42 public abstract class FragmentTuner
{
44 final Context mContext
;
47 public FragmentTuner(Context context
, State state
) {
52 public static FragmentTuner
pick(Context context
, State state
) {
53 switch (state
.action
) {
55 return new FilesTuner(context
, state
);
57 return new DocumentsTuner(context
, state
);
62 // Subtly different from isDocumentEnabled. The reason may be illuminated as follows.
63 // A folder is enabled such that it may be double clicked, even in settings
64 // when the folder itself cannot be selected. This may also be true of container types.
65 public boolean canSelectType(String docMimeType
, int docFlags
) {
69 public boolean isDocumentEnabled(String docMimeType
, int docFlags
) {
74 * When managed mode is enabled, active downloads will be visible in the UI.
75 * Presumably this should only be true when in the downloads directory.
77 boolean managedModeEnabled() {
82 * Whether drag n' drop is allowed in this context
84 boolean dragAndDropEnabled() {
88 abstract void updateActionMenu(Menu menu
, SelectionDetails selection
);
89 abstract void onModelLoaded(Model model
, @ResultType int resultType
, boolean isSearch
);
92 * Provides support for Platform specific specializations of DirectoryFragment.
94 private static final class DocumentsTuner
extends FragmentTuner
{
96 // We use this to keep track of whether a model has been previously loaded or not so we can
97 // open the drawer on empty directories on first launch
98 private boolean mModelPreviousLoaded
;
100 public DocumentsTuner(Context context
, State state
) {
101 super(context
, state
);
105 public boolean canSelectType(String docMimeType
, int docFlags
) {
106 if (!isDocumentEnabled(docMimeType
, docFlags
)) {
110 if (MimePredicate
.isDirectoryType(docMimeType
)) {
114 if (mState
.action
== ACTION_OPEN_TREE
115 || mState
.action
== ACTION_PICK_COPY_DESTINATION
) {
116 // In this case nothing *ever* is selectable...the expected user behavior is
117 // they navigate *into* a folder, then click a confirmation button indicating
118 // that the current directory is the directory they are picking.
126 public boolean isDocumentEnabled(String mimeType
, int docFlags
) {
127 // Directories are always enabled.
128 if (MimePredicate
.isDirectoryType(mimeType
)) {
132 switch (mState
.action
) {
134 // Read-only files are disabled when creating.
135 if ((docFlags
& Document
.FLAG_SUPPORTS_WRITE
) == 0) {
139 case ACTION_GET_CONTENT
:
140 final boolean isVirtual
= (docFlags
& Document
.FLAG_VIRTUAL_DOCUMENT
) != 0;
141 if (isVirtual
&& mState
.openableOnly
) {
146 return MimePredicate
.mimeMatches(mState
.acceptMimes
, mimeType
);
150 public void updateActionMenu(Menu menu
, SelectionDetails selection
) {
152 MenuItem open
= menu
.findItem(R
.id
.menu_open
);
153 MenuItem share
= menu
.findItem(R
.id
.menu_share
);
154 MenuItem delete
= menu
.findItem(R
.id
.menu_delete
);
155 MenuItem rename
= menu
.findItem(R
.id
.menu_rename
);
156 MenuItem selectAll
= menu
.findItem(R
.id
.menu_select_all
);
158 open
.setVisible(mState
.action
== ACTION_GET_CONTENT
159 || mState
.action
== ACTION_OPEN
);
160 share
.setVisible(false);
161 delete
.setVisible(false);
162 rename
.setVisible(false);
163 selectAll
.setVisible(mState
.allowMultiple
);
165 Menus
.disableHiddenItems(menu
);
169 void onModelLoaded(Model model
, @ResultType int resultType
, boolean isSearch
) {
170 boolean showDrawer
= false;
172 if (MimePredicate
.mimeMatches(MimePredicate
.VISUAL_MIMES
, mState
.acceptMimes
)) {
175 if (mState
.external
&& mState
.action
== ACTION_GET_CONTENT
) {
178 if (mState
.action
== ACTION_PICK_COPY_DESTINATION
) {
182 // When launched into empty root, open drawer.
183 if (model
.isEmpty()) {
187 if (showDrawer
&& !mState
.hasInitialLocationChanged() && !isSearch
188 && !mModelPreviousLoaded
) {
189 // This noops on layouts without drawer, so no need to guard.
190 ((BaseActivity
) mContext
).setRootsDrawerOpen(true);
192 mModelPreviousLoaded
= true;
197 * Provides support for Files activity specific specializations of DirectoryFragment.
199 private static final class FilesTuner
extends FragmentTuner
{
201 // We use this to keep track of whether a model has been previously loaded or not so we can
202 // open the drawer on empty directories on first launch
203 private boolean mModelPreviousLoaded
;
205 public FilesTuner(Context context
, State state
) {
206 super(context
, state
);
210 public void updateActionMenu(Menu menu
, SelectionDetails selection
) {
212 menu
.findItem(R
.id
.menu_open
).setVisible(false); // "open" is never used in Files.
214 // Commands accessible only via keyboard...
215 MenuItem copy
= menu
.findItem(R
.id
.menu_copy_to_clipboard
);
216 MenuItem paste
= menu
.findItem(R
.id
.menu_paste_from_clipboard
);
218 // Commands visible in the UI...
219 MenuItem rename
= menu
.findItem(R
.id
.menu_rename
);
220 MenuItem moveTo
= menu
.findItem(R
.id
.menu_move_to
);
221 MenuItem copyTo
= menu
.findItem(R
.id
.menu_copy_to
);
222 MenuItem share
= menu
.findItem(R
.id
.menu_share
);
223 MenuItem delete
= menu
.findItem(R
.id
.menu_delete
);
225 // copy is not visible, keyboard only
226 copy
.setEnabled(!selection
.containsPartialFiles());
228 // Commands usually on action-bar, so we always manage visibility.
229 share
.setVisible(!selection
.containsDirectories() && !selection
.containsPartialFiles());
230 delete
.setVisible(selection
.canDelete());
232 share
.setEnabled(!selection
.containsDirectories() && !selection
.containsPartialFiles());
233 delete
.setEnabled(selection
.canDelete());
235 // Commands always in overflow, so we don't bother showing/hiding...
236 copyTo
.setVisible(true);
237 moveTo
.setVisible(true);
238 rename
.setVisible(true);
240 copyTo
.setEnabled(!selection
.containsPartialFiles());
241 moveTo
.setEnabled(!selection
.containsPartialFiles() && selection
.canDelete());
242 rename
.setEnabled(!selection
.containsPartialFiles() && selection
.canRename());
244 Menus
.disableHiddenItems(menu
, copy
, paste
);
248 void onModelLoaded(Model model
, @ResultType int resultType
, boolean isSearch
) {
249 // When launched into empty root, open drawer.
250 if (model
.isEmpty() && !mState
.hasInitialLocationChanged() && !isSearch
251 && !mModelPreviousLoaded
) {
252 // This noops on layouts without drawer, so no need to guard.
253 ((BaseActivity
) mContext
).setRootsDrawerOpen(true);
255 mModelPreviousLoaded
= true;
259 public boolean managedModeEnabled() {
260 // When in downloads top level directory, we also show active downloads.
261 // And while we don't allow folders in Downloads, we do allow Zip files in
262 // downloads that themselves can be opened and viewed like directories.
263 // This method helps us understand when to kick in on those special behaviors.
264 return mState
.stack
.root
!= null
265 && mState
.stack
.root
.isDownloads()
266 && mState
.stack
.size() == 1;
270 public boolean dragAndDropEnabled() {
276 * Access to meta data about the selection.
278 interface SelectionDetails
{
279 boolean containsDirectories();
280 boolean containsPartialFiles();
282 // TODO: Update these to express characteristics instead of answering concrete questions,
283 // since the answer to those questions is (or can be) activity specific.