blob: 7b0510be644e01aab33bfdb41a8fee936638d3b4 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.documentsui.dirlist;
import static com.android.documentsui.State.ACTION_BROWSE;
import static com.android.documentsui.State.ACTION_CREATE;
import static com.android.documentsui.State.ACTION_GET_CONTENT;
import static com.android.documentsui.State.ACTION_OPEN;
import static com.android.documentsui.State.ACTION_OPEN_TREE;
import static com.android.documentsui.State.ACTION_PICK_COPY_DESTINATION;
import android.content.Context;
import android.provider.DocumentsContract.Document;
import android.view.Menu;
import android.view.MenuItem;
import com.android.documentsui.BaseActivity;
import com.android.documentsui.Menus;
import com.android.documentsui.MimePredicate;
import com.android.documentsui.R;
import com.android.documentsui.State;
import com.android.documentsui.dirlist.DirectoryFragment.ResultType;
/**
* Providers support for specializing the DirectoryFragment to the "host" Activity.
* Feel free to expand the role of this class to handle other specializations.
*/
public abstract class FragmentTuner {
final Context mContext;
final State mState;
public FragmentTuner(Context context, State state) {
mContext = context;
mState = state;
}
public static FragmentTuner pick(Context context, State state) {
switch (state.action) {
case ACTION_BROWSE:
return new FilesTuner(context, state);
default:
return new DocumentsTuner(context, state);
}
}
// Subtly different from isDocumentEnabled. The reason may be illuminated as follows.
// A folder is enabled such that it may be double clicked, even in settings
// when the folder itself cannot be selected. This may also be true of container types.
public boolean canSelectType(String docMimeType, int docFlags) {
return true;
}
public boolean isDocumentEnabled(String docMimeType, int docFlags) {
return true;
}
/**
* When managed mode is enabled, active downloads will be visible in the UI.
* Presumably this should only be true when in the downloads directory.
*/
boolean managedModeEnabled() {
return false;
}
/**
* Whether drag n' drop is allowed in this context
*/
boolean dragAndDropEnabled() {
return false;
}
abstract void updateActionMenu(Menu menu, SelectionDetails selection);
abstract void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch);
/**
* Provides support for Platform specific specializations of DirectoryFragment.
*/
private static final class DocumentsTuner extends FragmentTuner {
// We use this to keep track of whether a model has been previously loaded or not so we can
// open the drawer on empty directories on first launch
private boolean mModelPreviousLoaded;
public DocumentsTuner(Context context, State state) {
super(context, state);
}
@Override
public boolean canSelectType(String docMimeType, int docFlags) {
if (!isDocumentEnabled(docMimeType, docFlags)) {
return false;
}
if (MimePredicate.isDirectoryType(docMimeType)) {
return false;
}
if (mState.action == ACTION_OPEN_TREE
|| mState.action == ACTION_PICK_COPY_DESTINATION) {
// In this case nothing *ever* is selectable...the expected user behavior is
// they navigate *into* a folder, then click a confirmation button indicating
// that the current directory is the directory they are picking.
return false;
}
return true;
}
@Override
public boolean isDocumentEnabled(String mimeType, int docFlags) {
// Directories are always enabled.
if (MimePredicate.isDirectoryType(mimeType)) {
return true;
}
switch (mState.action) {
case ACTION_CREATE:
// Read-only files are disabled when creating.
if ((docFlags & Document.FLAG_SUPPORTS_WRITE) == 0) {
return false;
}
case ACTION_OPEN:
case ACTION_GET_CONTENT:
final boolean isVirtual = (docFlags & Document.FLAG_VIRTUAL_DOCUMENT) != 0;
if (isVirtual && mState.openableOnly) {
return false;
}
}
return MimePredicate.mimeMatches(mState.acceptMimes, mimeType);
}
@Override
public void updateActionMenu(Menu menu, SelectionDetails selection) {
MenuItem open = menu.findItem(R.id.menu_open);
MenuItem share = menu.findItem(R.id.menu_share);
MenuItem delete = menu.findItem(R.id.menu_delete);
MenuItem rename = menu.findItem(R.id.menu_rename);
MenuItem selectAll = menu.findItem(R.id.menu_select_all);
open.setVisible(mState.action == ACTION_GET_CONTENT
|| mState.action == ACTION_OPEN);
share.setVisible(false);
delete.setVisible(false);
rename.setVisible(false);
selectAll.setVisible(mState.allowMultiple);
Menus.disableHiddenItems(menu);
}
@Override
void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch) {
boolean showDrawer = false;
if (MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mState.acceptMimes)) {
showDrawer = false;
}
if (mState.external && mState.action == ACTION_GET_CONTENT) {
showDrawer = true;
}
if (mState.action == ACTION_PICK_COPY_DESTINATION) {
showDrawer = true;
}
// When launched into empty root, open drawer.
if (model.isEmpty()) {
showDrawer = true;
}
if (showDrawer && !mState.hasInitialLocationChanged() && !isSearch
&& !mModelPreviousLoaded) {
// This noops on layouts without drawer, so no need to guard.
((BaseActivity) mContext).setRootsDrawerOpen(true);
}
mModelPreviousLoaded = true;
}
}
/**
* Provides support for Files activity specific specializations of DirectoryFragment.
*/
private static final class FilesTuner extends FragmentTuner {
// We use this to keep track of whether a model has been previously loaded or not so we can
// open the drawer on empty directories on first launch
private boolean mModelPreviousLoaded;
public FilesTuner(Context context, State state) {
super(context, state);
}
@Override
public void updateActionMenu(Menu menu, SelectionDetails selection) {
menu.findItem(R.id.menu_open).setVisible(false); // "open" is never used in Files.
// Commands accessible only via keyboard...
MenuItem copy = menu.findItem(R.id.menu_copy_to_clipboard);
MenuItem paste = menu.findItem(R.id.menu_paste_from_clipboard);
// Commands visible in the UI...
MenuItem rename = menu.findItem(R.id.menu_rename);
MenuItem moveTo = menu.findItem(R.id.menu_move_to);
MenuItem copyTo = menu.findItem(R.id.menu_copy_to);
MenuItem share = menu.findItem(R.id.menu_share);
MenuItem delete = menu.findItem(R.id.menu_delete);
// copy is not visible, keyboard only
copy.setEnabled(!selection.containsPartialFiles());
// Commands usually on action-bar, so we always manage visibility.
share.setVisible(!selection.containsDirectories() && !selection.containsPartialFiles());
delete.setVisible(selection.canDelete());
share.setEnabled(!selection.containsDirectories() && !selection.containsPartialFiles());
delete.setEnabled(selection.canDelete());
// Commands always in overflow, so we don't bother showing/hiding...
copyTo.setVisible(true);
moveTo.setVisible(true);
rename.setVisible(true);
copyTo.setEnabled(!selection.containsPartialFiles());
moveTo.setEnabled(!selection.containsPartialFiles() && selection.canDelete());
rename.setEnabled(!selection.containsPartialFiles() && selection.canRename());
Menus.disableHiddenItems(menu, copy, paste);
}
@Override
void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch) {
// When launched into empty root, open drawer.
if (model.isEmpty() && !mState.hasInitialLocationChanged() && !isSearch
&& !mModelPreviousLoaded) {
// This noops on layouts without drawer, so no need to guard.
((BaseActivity) mContext).setRootsDrawerOpen(true);
}
mModelPreviousLoaded = true;
}
@Override
public boolean managedModeEnabled() {
// When in downloads top level directory, we also show active downloads.
// And while we don't allow folders in Downloads, we do allow Zip files in
// downloads that themselves can be opened and viewed like directories.
// This method helps us understand when to kick in on those special behaviors.
return mState.stack.root != null
&& mState.stack.root.isDownloads()
&& mState.stack.size() == 1;
}
@Override
public boolean dragAndDropEnabled() {
return true;
}
}
/**
* Access to meta data about the selection.
*/
interface SelectionDetails {
boolean containsDirectories();
boolean containsPartialFiles();
// TODO: Update these to express characteristics instead of answering concrete questions,
// since the answer to those questions is (or can be) activity specific.
boolean canDelete();
boolean canRename();
}
}