1 /* 2 * Copyright (C) 2015 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.dirlist; 18 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; 25 26 import android.content.Context; 27 import android.provider.DocumentsContract.Document; 28 import android.view.Menu; 29 import android.view.MenuItem; 30 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; 37 38 /** 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. 41 */ 42 public abstract class FragmentTuner { 43 44 final Context mContext; 45 final State mState; 46 FragmentTuner(Context context, State state)47 public FragmentTuner(Context context, State state) { 48 mContext = context; 49 mState = state; 50 } 51 pick(Context context, State state)52 public static FragmentTuner pick(Context context, State state) { 53 switch (state.action) { 54 case ACTION_BROWSE: 55 return new FilesTuner(context, state); 56 default: 57 return new DocumentsTuner(context, state); 58 } 59 } 60 61 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. canSelectType(String docMimeType, int docFlags)65 public boolean canSelectType(String docMimeType, int docFlags) { 66 return true; 67 } 68 isDocumentEnabled(String docMimeType, int docFlags)69 public boolean isDocumentEnabled(String docMimeType, int docFlags) { 70 return true; 71 } 72 73 /** 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. 76 */ managedModeEnabled()77 boolean managedModeEnabled() { 78 return false; 79 } 80 81 /** 82 * Whether drag n' drop is allowed in this context 83 */ dragAndDropEnabled()84 boolean dragAndDropEnabled() { 85 return false; 86 } 87 updateActionMenu(Menu menu, SelectionDetails selection)88 abstract void updateActionMenu(Menu menu, SelectionDetails selection); onModelLoaded(Model model, @ResultType int resultType, boolean isSearch)89 abstract void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch); 90 91 /** 92 * Provides support for Platform specific specializations of DirectoryFragment. 93 */ 94 private static final class DocumentsTuner extends FragmentTuner { 95 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; 99 DocumentsTuner(Context context, State state)100 public DocumentsTuner(Context context, State state) { 101 super(context, state); 102 } 103 104 @Override canSelectType(String docMimeType, int docFlags)105 public boolean canSelectType(String docMimeType, int docFlags) { 106 if (!isDocumentEnabled(docMimeType, docFlags)) { 107 return false; 108 } 109 110 if (MimePredicate.isDirectoryType(docMimeType)) { 111 return false; 112 } 113 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. 119 return false; 120 } 121 122 return true; 123 } 124 125 @Override isDocumentEnabled(String mimeType, int docFlags)126 public boolean isDocumentEnabled(String mimeType, int docFlags) { 127 // Directories are always enabled. 128 if (MimePredicate.isDirectoryType(mimeType)) { 129 return true; 130 } 131 132 switch (mState.action) { 133 case ACTION_CREATE: 134 // Read-only files are disabled when creating. 135 if ((docFlags & Document.FLAG_SUPPORTS_WRITE) == 0) { 136 return false; 137 } 138 case ACTION_OPEN: 139 case ACTION_GET_CONTENT: 140 final boolean isVirtual = (docFlags & Document.FLAG_VIRTUAL_DOCUMENT) != 0; 141 if (isVirtual && mState.openableOnly) { 142 return false; 143 } 144 } 145 146 return MimePredicate.mimeMatches(mState.acceptMimes, mimeType); 147 } 148 149 @Override updateActionMenu(Menu menu, SelectionDetails selection)150 public void updateActionMenu(Menu menu, SelectionDetails selection) { 151 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); 157 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); 164 165 Menus.disableHiddenItems(menu); 166 } 167 168 @Override onModelLoaded(Model model, @ResultType int resultType, boolean isSearch)169 void onModelLoaded(Model model, @ResultType int resultType, boolean isSearch) { 170 boolean showDrawer = false; 171 172 if (MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mState.acceptMimes)) { 173 showDrawer = false; 174 } 175 if (mState.external && mState.action == ACTION_GET_CONTENT) { 176 showDrawer = true; 177 } 178 if (mState.action == ACTION_PICK_COPY_DESTINATION) { 179 showDrawer = true; 180 } 181 182 // When launched into empty root, open drawer. 183 if (model.isEmpty()) { 184 showDrawer = true; 185 } 186 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); 191 } 192 mModelPreviousLoaded = true; 193 } 194 } 195 196 /** 197 * Provides support for Files activity specific specializations of DirectoryFragment. 198 */ 199 private static final class FilesTuner extends FragmentTuner { 200 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; 204 FilesTuner(Context context, State state)205 public FilesTuner(Context context, State state) { 206 super(context, state); 207 } 208 209 @Override updateActionMenu(Menu menu, SelectionDetails selection)210 public void updateActionMenu(Menu menu, SelectionDetails selection) { 211 212 menu.findItem(R.id.menu_open).setVisible(false); // "open" is never used in Files. 213 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); 217 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); 224 225 // copy is not visible, keyboard only 226 copy.setEnabled(!selection.containsPartialFiles()); 227 228 // Commands usually on action-bar, so we always manage visibility. 229 share.setVisible(!selection.containsDirectories() && !selection.containsPartialFiles()); 230 delete.setVisible(selection.canDelete()); 231 232 share.setEnabled(!selection.containsDirectories() && !selection.containsPartialFiles()); 233 delete.setEnabled(selection.canDelete()); 234 235 // Commands always in overflow, so we don't bother showing/hiding... 236 copyTo.setVisible(true); 237 moveTo.setVisible(true); 238 rename.setVisible(true); 239 240 copyTo.setEnabled(!selection.containsPartialFiles()); 241 moveTo.setEnabled(!selection.containsPartialFiles() && selection.canDelete()); 242 rename.setEnabled(!selection.containsPartialFiles() && selection.canRename()); 243 244 Menus.disableHiddenItems(menu, copy, paste); 245 } 246 247 @Override onModelLoaded(Model model, @ResultType int resultType, boolean isSearch)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); 254 } 255 mModelPreviousLoaded = true; 256 } 257 258 @Override managedModeEnabled()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; 267 } 268 269 @Override dragAndDropEnabled()270 public boolean dragAndDropEnabled() { 271 return true; 272 } 273 } 274 275 /** 276 * Access to meta data about the selection. 277 */ 278 interface SelectionDetails { containsDirectories()279 boolean containsDirectories(); containsPartialFiles()280 boolean containsPartialFiles(); 281 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. canDelete()284 boolean canDelete(); canRename()285 boolean canRename(); 286 } 287 } 288