Home | History | Annotate | Download | only in dirlist
      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 
     47     public FragmentTuner(Context context, State state) {
     48         mContext = context;
     49         mState = state;
     50     }
     51 
     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.
     65     public boolean canSelectType(String docMimeType, int docFlags) {
     66         return true;
     67     }
     68 
     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      */
     77     boolean managedModeEnabled() {
     78         return false;
     79     }
     80 
     81     /**
     82      * Whether drag n' drop is allowed in this context
     83      */
     84     boolean dragAndDropEnabled() {
     85         return false;
     86     }
     87 
     88     abstract void updateActionMenu(Menu menu, SelectionDetails selection);
     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 
    100         public DocumentsTuner(Context context, State state) {
    101             super(context, state);
    102         }
    103 
    104         @Override
    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
    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
    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
    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 
    205         public FilesTuner(Context context, State state) {
    206             super(context, state);
    207         }
    208 
    209         @Override
    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
    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
    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
    270         public boolean dragAndDropEnabled() {
    271             return true;
    272         }
    273     }
    274 
    275     /**
    276      * Access to meta data about the selection.
    277      */
    278     interface SelectionDetails {
    279         boolean containsDirectories();
    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.
    284         boolean canDelete();
    285         boolean canRename();
    286     }
    287 }
    288