Home | History | Annotate | Download | only in documentsui
      1 /*
      2  * Copyright (C) 2013 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 static com.android.documentsui.BaseActivity.State.ACTION_BROWSE;
     20 import static com.android.documentsui.BaseActivity.State.ACTION_BROWSE_ALL;
     21 import static com.android.documentsui.BaseActivity.State.ACTION_CREATE;
     22 import static com.android.documentsui.BaseActivity.State.ACTION_MANAGE;
     23 import static com.android.documentsui.BaseActivity.State.MODE_GRID;
     24 import static com.android.documentsui.BaseActivity.State.MODE_LIST;
     25 import static com.android.documentsui.BaseActivity.State.MODE_UNKNOWN;
     26 import static com.android.documentsui.BaseActivity.State.SORT_ORDER_UNKNOWN;
     27 import static com.android.documentsui.DocumentsActivity.TAG;
     28 import static com.android.documentsui.model.DocumentInfo.getCursorInt;
     29 import static com.android.documentsui.model.DocumentInfo.getCursorLong;
     30 import static com.android.documentsui.model.DocumentInfo.getCursorString;
     31 import android.app.Activity;
     32 import android.app.ActivityManager;
     33 import android.app.Fragment;
     34 import android.app.FragmentManager;
     35 import android.app.FragmentTransaction;
     36 import android.app.LoaderManager.LoaderCallbacks;
     37 import android.content.ContentProviderClient;
     38 import android.content.ContentResolver;
     39 import android.content.ContentValues;
     40 import android.content.Context;
     41 import android.content.Intent;
     42 import android.content.Loader;
     43 import android.content.res.Resources;
     44 import android.database.Cursor;
     45 import android.graphics.Bitmap;
     46 import android.graphics.Point;
     47 import android.graphics.drawable.Drawable;
     48 import android.graphics.drawable.InsetDrawable;
     49 import android.net.Uri;
     50 import android.os.AsyncTask;
     51 import android.os.Bundle;
     52 import android.os.CancellationSignal;
     53 import android.os.Handler;
     54 import android.os.Looper;
     55 import android.os.OperationCanceledException;
     56 import android.os.Parcelable;
     57 import android.provider.DocumentsContract;
     58 import android.provider.DocumentsContract.Document;
     59 import android.text.TextUtils;
     60 import android.text.format.DateUtils;
     61 import android.text.format.Formatter;
     62 import android.text.format.Time;
     63 import android.util.Log;
     64 import android.util.SparseArray;
     65 import android.util.SparseBooleanArray;
     66 import android.view.ActionMode;
     67 import android.view.LayoutInflater;
     68 import android.view.Menu;
     69 import android.view.MenuItem;
     70 import android.view.View;
     71 import android.view.ViewGroup;
     72 import android.widget.AbsListView;
     73 import android.widget.AbsListView.MultiChoiceModeListener;
     74 import android.widget.AbsListView.RecyclerListener;
     75 import android.widget.AdapterView;
     76 import android.widget.AdapterView.OnItemClickListener;
     77 import android.widget.BaseAdapter;
     78 import android.widget.GridView;
     79 import android.widget.ImageView;
     80 import android.widget.ListView;
     81 import android.widget.TextView;
     82 import android.widget.Toast;
     83 
     84 import com.android.documentsui.BaseActivity.State;
     85 import com.android.documentsui.ProviderExecutor.Preemptable;
     86 import com.android.documentsui.RecentsProvider.StateColumns;
     87 import com.android.documentsui.model.DocumentInfo;
     88 import com.android.documentsui.model.DocumentStack;
     89 import com.android.documentsui.model.RootInfo;
     90 import com.google.android.collect.Lists;
     91 
     92 import java.util.ArrayList;
     93 import java.util.List;
     94 
     95 /**
     96  * Display the documents inside a single directory.
     97  */
     98 public class DirectoryFragment extends Fragment {
     99 
    100     private View mEmptyView;
    101     private ListView mListView;
    102     private GridView mGridView;
    103 
    104     private AbsListView mCurrentView;
    105 
    106     public static final int TYPE_NORMAL = 1;
    107     public static final int TYPE_SEARCH = 2;
    108     public static final int TYPE_RECENT_OPEN = 3;
    109 
    110     public static final int ANIM_NONE = 1;
    111     public static final int ANIM_SIDE = 2;
    112     public static final int ANIM_DOWN = 3;
    113     public static final int ANIM_UP = 4;
    114 
    115     public static final int REQUEST_COPY_DESTINATION = 1;
    116 
    117     private int mType = TYPE_NORMAL;
    118     private String mStateKey;
    119 
    120     private int mLastMode = MODE_UNKNOWN;
    121     private int mLastSortOrder = SORT_ORDER_UNKNOWN;
    122     private boolean mLastShowSize = false;
    123 
    124     private boolean mHideGridTitles = false;
    125 
    126     private boolean mSvelteRecents;
    127     private Point mThumbSize;
    128 
    129     private DocumentsAdapter mAdapter;
    130     private LoaderCallbacks<DirectoryResult> mCallbacks;
    131 
    132     private static final String EXTRA_TYPE = "type";
    133     private static final String EXTRA_ROOT = "root";
    134     private static final String EXTRA_DOC = "doc";
    135     private static final String EXTRA_QUERY = "query";
    136     private static final String EXTRA_IGNORE_STATE = "ignoreState";
    137 
    138     private final int mLoaderId = 42;
    139 
    140     private final Handler mHandler = new Handler(Looper.getMainLooper());
    141 
    142     public static void showNormal(FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) {
    143         show(fm, TYPE_NORMAL, root, doc, null, anim);
    144     }
    145 
    146     public static void showSearch(FragmentManager fm, RootInfo root, String query, int anim) {
    147         show(fm, TYPE_SEARCH, root, null, query, anim);
    148     }
    149 
    150     public static void showRecentsOpen(FragmentManager fm, int anim) {
    151         show(fm, TYPE_RECENT_OPEN, null, null, null, anim);
    152     }
    153 
    154     private static void show(FragmentManager fm, int type, RootInfo root, DocumentInfo doc,
    155             String query, int anim) {
    156         final Bundle args = new Bundle();
    157         args.putInt(EXTRA_TYPE, type);
    158         args.putParcelable(EXTRA_ROOT, root);
    159         args.putParcelable(EXTRA_DOC, doc);
    160         args.putString(EXTRA_QUERY, query);
    161 
    162         final FragmentTransaction ft = fm.beginTransaction();
    163         switch (anim) {
    164             case ANIM_SIDE:
    165                 args.putBoolean(EXTRA_IGNORE_STATE, true);
    166                 break;
    167             case ANIM_DOWN:
    168                 args.putBoolean(EXTRA_IGNORE_STATE, true);
    169                 ft.setCustomAnimations(R.animator.dir_down, R.animator.dir_frozen);
    170                 break;
    171             case ANIM_UP:
    172                 ft.setCustomAnimations(R.animator.dir_frozen, R.animator.dir_up);
    173                 break;
    174         }
    175 
    176         final DirectoryFragment fragment = new DirectoryFragment();
    177         fragment.setArguments(args);
    178 
    179         ft.replace(R.id.container_directory, fragment);
    180         ft.commitAllowingStateLoss();
    181     }
    182 
    183     private static String buildStateKey(RootInfo root, DocumentInfo doc) {
    184         final StringBuilder builder = new StringBuilder();
    185         builder.append(root != null ? root.authority : "null").append(';');
    186         builder.append(root != null ? root.rootId : "null").append(';');
    187         builder.append(doc != null ? doc.documentId : "null");
    188         return builder.toString();
    189     }
    190 
    191     public static DirectoryFragment get(FragmentManager fm) {
    192         // TODO: deal with multiple directories shown at once
    193         return (DirectoryFragment) fm.findFragmentById(R.id.container_directory);
    194     }
    195 
    196     @Override
    197     public View onCreateView(
    198             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    199         final Context context = inflater.getContext();
    200         final Resources res = context.getResources();
    201         final View view = inflater.inflate(R.layout.fragment_directory, container, false);
    202 
    203         mEmptyView = view.findViewById(android.R.id.empty);
    204 
    205         mListView = (ListView) view.findViewById(R.id.list);
    206         mListView.setOnItemClickListener(mItemListener);
    207         mListView.setMultiChoiceModeListener(mMultiListener);
    208         mListView.setRecyclerListener(mRecycleListener);
    209 
    210         // Indent our list divider to align with text
    211         final Drawable divider = mListView.getDivider();
    212         final boolean insetLeft = res.getBoolean(R.bool.list_divider_inset_left);
    213         final int insetSize = res.getDimensionPixelSize(R.dimen.list_divider_inset);
    214         if (insetLeft) {
    215             mListView.setDivider(new InsetDrawable(divider, insetSize, 0, 0, 0));
    216         } else {
    217             mListView.setDivider(new InsetDrawable(divider, 0, 0, insetSize, 0));
    218         }
    219 
    220         mGridView = (GridView) view.findViewById(R.id.grid);
    221         mGridView.setOnItemClickListener(mItemListener);
    222         mGridView.setMultiChoiceModeListener(mMultiListener);
    223         mGridView.setRecyclerListener(mRecycleListener);
    224 
    225         return view;
    226     }
    227 
    228     @Override
    229     public void onDestroyView() {
    230         super.onDestroyView();
    231 
    232         // Cancel any outstanding thumbnail requests
    233         final ViewGroup target = (mListView.getAdapter() != null) ? mListView : mGridView;
    234         final int count = target.getChildCount();
    235         for (int i = 0; i < count; i++) {
    236             final View view = target.getChildAt(i);
    237             mRecycleListener.onMovedToScrapHeap(view);
    238         }
    239 
    240         // Tear down any selection in progress
    241         mListView.setChoiceMode(AbsListView.CHOICE_MODE_NONE);
    242         mGridView.setChoiceMode(AbsListView.CHOICE_MODE_NONE);
    243     }
    244 
    245     @Override
    246     public void onActivityCreated(Bundle savedInstanceState) {
    247         super.onActivityCreated(savedInstanceState);
    248 
    249         final Context context = getActivity();
    250         final State state = getDisplayState(DirectoryFragment.this);
    251 
    252         final RootInfo root = getArguments().getParcelable(EXTRA_ROOT);
    253         final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);
    254 
    255         mAdapter = new DocumentsAdapter();
    256         mType = getArguments().getInt(EXTRA_TYPE);
    257         mStateKey = buildStateKey(root, doc);
    258 
    259         if (mType == TYPE_RECENT_OPEN) {
    260             // Hide titles when showing recents for picking images/videos
    261             mHideGridTitles = MimePredicate.mimeMatches(
    262                     MimePredicate.VISUAL_MIMES, state.acceptMimes);
    263         } else {
    264             mHideGridTitles = (doc != null) && doc.isGridTitlesHidden();
    265         }
    266 
    267         final ActivityManager am = (ActivityManager) context.getSystemService(
    268                 Context.ACTIVITY_SERVICE);
    269         mSvelteRecents = am.isLowRamDevice() && (mType == TYPE_RECENT_OPEN);
    270 
    271         mCallbacks = new LoaderCallbacks<DirectoryResult>() {
    272             @Override
    273             public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
    274                 final String query = getArguments().getString(EXTRA_QUERY);
    275 
    276                 Uri contentsUri;
    277                 switch (mType) {
    278                     case TYPE_NORMAL:
    279                         contentsUri = DocumentsContract.buildChildDocumentsUri(
    280                                 doc.authority, doc.documentId);
    281                         if (state.action == ACTION_MANAGE) {
    282                             contentsUri = DocumentsContract.setManageMode(contentsUri);
    283                         }
    284                         return new DirectoryLoader(
    285                                 context, mType, root, doc, contentsUri, state.userSortOrder);
    286                     case TYPE_SEARCH:
    287                         contentsUri = DocumentsContract.buildSearchDocumentsUri(
    288                                 root.authority, root.rootId, query);
    289                         if (state.action == ACTION_MANAGE) {
    290                             contentsUri = DocumentsContract.setManageMode(contentsUri);
    291                         }
    292                         return new DirectoryLoader(
    293                                 context, mType, root, doc, contentsUri, state.userSortOrder);
    294                     case TYPE_RECENT_OPEN:
    295                         final RootsCache roots = DocumentsApplication.getRootsCache(context);
    296                         return new RecentLoader(context, roots, state);
    297                     default:
    298                         throw new IllegalStateException("Unknown type " + mType);
    299                 }
    300             }
    301 
    302             @Override
    303             public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
    304                 if (result == null || result.exception != null) {
    305                     // onBackPressed does a fragment transaction, which can't be done inside
    306                     // onLoadFinished
    307                     mHandler.post(new Runnable() {
    308                         @Override
    309                         public void run() {
    310                             final Activity activity = getActivity();
    311                             if (activity != null) {
    312                                 activity.onBackPressed();
    313                             }
    314                         }
    315                     });
    316                     return;
    317                 }
    318 
    319                 if (!isAdded()) return;
    320 
    321                 mAdapter.swapResult(result);
    322 
    323                 // Push latest state up to UI
    324                 // TODO: if mode change was racing with us, don't overwrite it
    325                 if (result.mode != MODE_UNKNOWN) {
    326                     state.derivedMode = result.mode;
    327                 }
    328                 state.derivedSortOrder = result.sortOrder;
    329                 ((BaseActivity) context).onStateChanged();
    330 
    331                 updateDisplayState();
    332 
    333                 // When launched into empty recents, show drawer
    334                 if (mType == TYPE_RECENT_OPEN && mAdapter.isEmpty() && !state.stackTouched &&
    335                         context instanceof DocumentsActivity) {
    336                     ((DocumentsActivity) context).setRootsDrawerOpen(true);
    337                 }
    338 
    339                 // Restore any previous instance state
    340                 final SparseArray<Parcelable> container = state.dirState.remove(mStateKey);
    341                 if (container != null && !getArguments().getBoolean(EXTRA_IGNORE_STATE, false)) {
    342                     getView().restoreHierarchyState(container);
    343                 } else if (mLastSortOrder != state.derivedSortOrder) {
    344                     mListView.smoothScrollToPosition(0);
    345                     mGridView.smoothScrollToPosition(0);
    346                 }
    347 
    348                 mLastSortOrder = state.derivedSortOrder;
    349             }
    350 
    351             @Override
    352             public void onLoaderReset(Loader<DirectoryResult> loader) {
    353                 mAdapter.swapResult(null);
    354             }
    355         };
    356 
    357         // Kick off loader at least once
    358         getLoaderManager().restartLoader(mLoaderId, null, mCallbacks);
    359 
    360         updateDisplayState();
    361     }
    362 
    363     @Override
    364     public void onActivityResult(int requestCode, int resultCode, Intent data) {
    365         // There's only one request code right now. Replace this with a switch statement or
    366         // something more scalable when more codes are added.
    367         if (requestCode != REQUEST_COPY_DESTINATION) {
    368             return;
    369         }
    370         if (resultCode == Activity.RESULT_CANCELED || data == null) {
    371             // User pressed the back button or otherwise cancelled the destination pick. Don't
    372             // proceed with the copy.
    373             return;
    374         }
    375 
    376         CopyService.start(getActivity(), getDisplayState(this).selectedDocumentsForCopy,
    377                 (DocumentStack) data.getParcelableExtra(CopyService.EXTRA_STACK));
    378     }
    379 
    380     @Override
    381     public void onStop() {
    382         super.onStop();
    383 
    384         // Remember last scroll location
    385         final SparseArray<Parcelable> container = new SparseArray<Parcelable>();
    386         getView().saveHierarchyState(container);
    387         final State state = getDisplayState(this);
    388         state.dirState.put(mStateKey, container);
    389     }
    390 
    391     @Override
    392     public void onResume() {
    393         super.onResume();
    394         updateDisplayState();
    395     }
    396 
    397     public void onDisplayStateChanged() {
    398         updateDisplayState();
    399     }
    400 
    401     public void onUserSortOrderChanged() {
    402         // Sort order change always triggers reload; we'll trigger state change
    403         // on the flip side.
    404         getLoaderManager().restartLoader(mLoaderId, null, mCallbacks);
    405     }
    406 
    407     public void onUserModeChanged() {
    408         final ContentResolver resolver = getActivity().getContentResolver();
    409         final State state = getDisplayState(this);
    410 
    411         final RootInfo root = getArguments().getParcelable(EXTRA_ROOT);
    412         final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);
    413 
    414         if (root != null && doc != null) {
    415             final Uri stateUri = RecentsProvider.buildState(
    416                     root.authority, root.rootId, doc.documentId);
    417             final ContentValues values = new ContentValues();
    418             values.put(StateColumns.MODE, state.userMode);
    419 
    420             new AsyncTask<Void, Void, Void>() {
    421                 @Override
    422                 protected Void doInBackground(Void... params) {
    423                     resolver.insert(stateUri, values);
    424                     return null;
    425                 }
    426             }.execute();
    427         }
    428 
    429         // Mode change is just visual change; no need to kick loader, and
    430         // deliver change event immediately.
    431         state.derivedMode = state.userMode;
    432         ((BaseActivity) getActivity()).onStateChanged();
    433 
    434         updateDisplayState();
    435     }
    436 
    437     private void updateDisplayState() {
    438         final State state = getDisplayState(this);
    439 
    440         if (mLastMode == state.derivedMode && mLastShowSize == state.showSize) return;
    441         mLastMode = state.derivedMode;
    442         mLastShowSize = state.showSize;
    443 
    444         mListView.setVisibility(state.derivedMode == MODE_LIST ? View.VISIBLE : View.GONE);
    445         mGridView.setVisibility(state.derivedMode == MODE_GRID ? View.VISIBLE : View.GONE);
    446 
    447         final int choiceMode;
    448         if (state.allowMultiple) {
    449             choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL;
    450         } else {
    451             choiceMode = ListView.CHOICE_MODE_NONE;
    452         }
    453 
    454         final int thumbSize;
    455         if (state.derivedMode == MODE_GRID) {
    456             thumbSize = getResources().getDimensionPixelSize(R.dimen.grid_width);
    457             mListView.setAdapter(null);
    458             mListView.setChoiceMode(ListView.CHOICE_MODE_NONE);
    459             mGridView.setAdapter(mAdapter);
    460             mGridView.setColumnWidth(getResources().getDimensionPixelSize(R.dimen.grid_width));
    461             mGridView.setNumColumns(GridView.AUTO_FIT);
    462             mGridView.setChoiceMode(choiceMode);
    463             mCurrentView = mGridView;
    464         } else if (state.derivedMode == MODE_LIST) {
    465             thumbSize = getResources().getDimensionPixelSize(R.dimen.icon_size);
    466             mGridView.setAdapter(null);
    467             mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE);
    468             mListView.setAdapter(mAdapter);
    469             mListView.setChoiceMode(choiceMode);
    470             mCurrentView = mListView;
    471         } else {
    472             throw new IllegalStateException("Unknown state " + state.derivedMode);
    473         }
    474 
    475         mThumbSize = new Point(thumbSize, thumbSize);
    476     }
    477 
    478     private OnItemClickListener mItemListener = new OnItemClickListener() {
    479         @Override
    480         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    481             final Cursor cursor = mAdapter.getItem(position);
    482             if (cursor != null) {
    483                 final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
    484                 final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
    485                 if (isDocumentEnabled(docMimeType, docFlags)) {
    486                     final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
    487                     ((BaseActivity) getActivity()).onDocumentPicked(doc);
    488                 }
    489             }
    490         }
    491     };
    492 
    493     private MultiChoiceModeListener mMultiListener = new MultiChoiceModeListener() {
    494         @Override
    495         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
    496             mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
    497             mode.setTitle(TextUtils.formatSelectedCount(mCurrentView.getCheckedItemCount()));
    498             return true;
    499         }
    500 
    501         @Override
    502         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
    503             final State state = getDisplayState(DirectoryFragment.this);
    504 
    505             final MenuItem open = menu.findItem(R.id.menu_open);
    506             final MenuItem share = menu.findItem(R.id.menu_share);
    507             final MenuItem delete = menu.findItem(R.id.menu_delete);
    508             final MenuItem copy = menu.findItem(R.id.menu_copy);
    509 
    510             final boolean manageOrBrowse = (state.action == ACTION_MANAGE
    511                     || state.action == ACTION_BROWSE || state.action == ACTION_BROWSE_ALL);
    512 
    513             open.setVisible(!manageOrBrowse);
    514             share.setVisible(manageOrBrowse);
    515             delete.setVisible(manageOrBrowse);
    516             // Disable copying from the Recents view.
    517             copy.setVisible(manageOrBrowse && mType != TYPE_RECENT_OPEN);
    518 
    519             return true;
    520         }
    521 
    522         @Override
    523         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    524             final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions();
    525             final ArrayList<DocumentInfo> docs = Lists.newArrayList();
    526             final int size = checked.size();
    527             for (int i = 0; i < size; i++) {
    528                 if (checked.valueAt(i)) {
    529                     final Cursor cursor = mAdapter.getItem(checked.keyAt(i));
    530                     final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
    531                     docs.add(doc);
    532                 }
    533             }
    534 
    535             final int id = item.getItemId();
    536             if (id == R.id.menu_open) {
    537                 BaseActivity.get(DirectoryFragment.this).onDocumentsPicked(docs);
    538                 mode.finish();
    539                 return true;
    540 
    541             } else if (id == R.id.menu_share) {
    542                 onShareDocuments(docs);
    543                 mode.finish();
    544                 return true;
    545 
    546             } else if (id == R.id.menu_delete) {
    547                 onDeleteDocuments(docs);
    548                 mode.finish();
    549                 return true;
    550 
    551             } else if (id == R.id.menu_copy) {
    552                 onCopyDocuments(docs);
    553                 mode.finish();
    554                 return true;
    555 
    556             } else if (id == R.id.menu_select_all) {
    557                 int count = mCurrentView.getCount();
    558                 for (int i = 0; i < count; i++) {
    559                     mCurrentView.setItemChecked(i, true);
    560                 }
    561                 updateDisplayState();
    562                 return true;
    563 
    564             } else {
    565                 return false;
    566             }
    567         }
    568 
    569         @Override
    570         public void onDestroyActionMode(ActionMode mode) {
    571             // ignored
    572         }
    573 
    574         @Override
    575         public void onItemCheckedStateChanged(
    576                 ActionMode mode, int position, long id, boolean checked) {
    577             if (checked) {
    578                 // Directories and footer items cannot be checked
    579                 boolean valid = false;
    580 
    581                 final Cursor cursor = mAdapter.getItem(position);
    582                 if (cursor != null) {
    583                     final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
    584                     final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
    585                     valid = isDocumentEnabled(docMimeType, docFlags);
    586                 }
    587 
    588                 if (!valid) {
    589                     mCurrentView.setItemChecked(position, false);
    590                 }
    591             }
    592 
    593             mode.setTitle(TextUtils.formatSelectedCount(mCurrentView.getCheckedItemCount()));
    594         }
    595     };
    596 
    597     private RecyclerListener mRecycleListener = new RecyclerListener() {
    598         @Override
    599         public void onMovedToScrapHeap(View view) {
    600             final ImageView iconThumb = (ImageView) view.findViewById(R.id.icon_thumb);
    601             if (iconThumb != null) {
    602                 final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) iconThumb.getTag();
    603                 if (oldTask != null) {
    604                     oldTask.preempt();
    605                     iconThumb.setTag(null);
    606                 }
    607             }
    608         }
    609     };
    610 
    611     private void onShareDocuments(List<DocumentInfo> docs) {
    612         Intent intent;
    613 
    614         // Filter out directories - those can't be shared.
    615         List<DocumentInfo> docsForSend = Lists.newArrayList();
    616         for (DocumentInfo doc: docs) {
    617             if (!Document.MIME_TYPE_DIR.equals(doc.mimeType)) {
    618                 docsForSend.add(doc);
    619             }
    620         }
    621 
    622         if (docsForSend.size() == 1) {
    623             final DocumentInfo doc = docsForSend.get(0);
    624 
    625             intent = new Intent(Intent.ACTION_SEND);
    626             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    627             intent.addCategory(Intent.CATEGORY_DEFAULT);
    628             intent.setType(doc.mimeType);
    629             intent.putExtra(Intent.EXTRA_STREAM, doc.derivedUri);
    630 
    631         } else if (docsForSend.size() > 1) {
    632             intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
    633             intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    634             intent.addCategory(Intent.CATEGORY_DEFAULT);
    635 
    636             final ArrayList<String> mimeTypes = Lists.newArrayList();
    637             final ArrayList<Uri> uris = Lists.newArrayList();
    638             for (DocumentInfo doc : docsForSend) {
    639                 mimeTypes.add(doc.mimeType);
    640                 uris.add(doc.derivedUri);
    641             }
    642 
    643             intent.setType(findCommonMimeType(mimeTypes));
    644             intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
    645 
    646         } else {
    647             return;
    648         }
    649 
    650         intent = Intent.createChooser(intent, getActivity().getText(R.string.share_via));
    651         startActivity(intent);
    652     }
    653 
    654     private void onDeleteDocuments(List<DocumentInfo> docs) {
    655         final Context context = getActivity();
    656         final ContentResolver resolver = context.getContentResolver();
    657 
    658         boolean hadTrouble = false;
    659         for (DocumentInfo doc : docs) {
    660             if (!doc.isDeleteSupported()) {
    661                 Log.w(TAG, "Skipping " + doc);
    662                 hadTrouble = true;
    663                 continue;
    664             }
    665 
    666             ContentProviderClient client = null;
    667             try {
    668                 client = DocumentsApplication.acquireUnstableProviderOrThrow(
    669                         resolver, doc.derivedUri.getAuthority());
    670                 DocumentsContract.deleteDocument(client, doc.derivedUri);
    671             } catch (Exception e) {
    672                 Log.w(TAG, "Failed to delete " + doc);
    673                 hadTrouble = true;
    674             } finally {
    675                 ContentProviderClient.releaseQuietly(client);
    676             }
    677         }
    678 
    679         if (hadTrouble) {
    680             Toast.makeText(context, R.string.toast_failed_delete, Toast.LENGTH_SHORT).show();
    681         }
    682     }
    683 
    684     private void onCopyDocuments(List<DocumentInfo> docs) {
    685         getDisplayState(this).selectedDocumentsForCopy = docs;
    686 
    687         // Pop up a dialog to pick a destination.  This is inadequate but works for now.
    688         // TODO: Implement a picker that is to spec.
    689         final Intent intent = new Intent(
    690                 BaseActivity.DocumentsIntent.ACTION_OPEN_COPY_DESTINATION,
    691                 Uri.EMPTY,
    692                 getActivity(),
    693                 DocumentsActivity.class);
    694         boolean directoryCopy = false;
    695         for (DocumentInfo info : docs) {
    696             if (Document.MIME_TYPE_DIR.equals(info.mimeType)) {
    697                 directoryCopy = true;
    698                 break;
    699             }
    700         }
    701         intent.putExtra(BaseActivity.DocumentsIntent.EXTRA_DIRECTORY_COPY, directoryCopy);
    702         startActivityForResult(intent, REQUEST_COPY_DESTINATION);
    703     }
    704 
    705     private static State getDisplayState(Fragment fragment) {
    706         return ((BaseActivity) fragment.getActivity()).getDisplayState();
    707     }
    708 
    709     private static abstract class Footer {
    710         private final int mItemViewType;
    711 
    712         public Footer(int itemViewType) {
    713             mItemViewType = itemViewType;
    714         }
    715 
    716         public abstract View getView(View convertView, ViewGroup parent);
    717 
    718         public int getItemViewType() {
    719             return mItemViewType;
    720         }
    721     }
    722 
    723     private class LoadingFooter extends Footer {
    724         public LoadingFooter() {
    725             super(1);
    726         }
    727 
    728         @Override
    729         public View getView(View convertView, ViewGroup parent) {
    730             final Context context = parent.getContext();
    731             final State state = getDisplayState(DirectoryFragment.this);
    732 
    733             if (convertView == null) {
    734                 final LayoutInflater inflater = LayoutInflater.from(context);
    735                 if (state.derivedMode == MODE_LIST) {
    736                     convertView = inflater.inflate(R.layout.item_loading_list, parent, false);
    737                 } else if (state.derivedMode == MODE_GRID) {
    738                     convertView = inflater.inflate(R.layout.item_loading_grid, parent, false);
    739                 } else {
    740                     throw new IllegalStateException();
    741                 }
    742             }
    743 
    744             return convertView;
    745         }
    746     }
    747 
    748     private class MessageFooter extends Footer {
    749         private final int mIcon;
    750         private final String mMessage;
    751 
    752         public MessageFooter(int itemViewType, int icon, String message) {
    753             super(itemViewType);
    754             mIcon = icon;
    755             mMessage = message;
    756         }
    757 
    758         @Override
    759         public View getView(View convertView, ViewGroup parent) {
    760             final Context context = parent.getContext();
    761             final State state = getDisplayState(DirectoryFragment.this);
    762 
    763             if (convertView == null) {
    764                 final LayoutInflater inflater = LayoutInflater.from(context);
    765                 if (state.derivedMode == MODE_LIST) {
    766                     convertView = inflater.inflate(R.layout.item_message_list, parent, false);
    767                 } else if (state.derivedMode == MODE_GRID) {
    768                     convertView = inflater.inflate(R.layout.item_message_grid, parent, false);
    769                 } else {
    770                     throw new IllegalStateException();
    771                 }
    772             }
    773 
    774             final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
    775             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
    776             icon.setImageResource(mIcon);
    777             title.setText(mMessage);
    778             return convertView;
    779         }
    780     }
    781 
    782     private class DocumentsAdapter extends BaseAdapter {
    783         private Cursor mCursor;
    784         private int mCursorCount;
    785 
    786         private List<Footer> mFooters = Lists.newArrayList();
    787 
    788         public void swapResult(DirectoryResult result) {
    789             mCursor = result != null ? result.cursor : null;
    790             mCursorCount = mCursor != null ? mCursor.getCount() : 0;
    791 
    792             mFooters.clear();
    793 
    794             final Bundle extras = mCursor != null ? mCursor.getExtras() : null;
    795             if (extras != null) {
    796                 final String info = extras.getString(DocumentsContract.EXTRA_INFO);
    797                 if (info != null) {
    798                     mFooters.add(new MessageFooter(2, R.drawable.ic_dialog_info, info));
    799                 }
    800                 final String error = extras.getString(DocumentsContract.EXTRA_ERROR);
    801                 if (error != null) {
    802                     mFooters.add(new MessageFooter(3, R.drawable.ic_dialog_alert, error));
    803                 }
    804                 if (extras.getBoolean(DocumentsContract.EXTRA_LOADING, false)) {
    805                     mFooters.add(new LoadingFooter());
    806                 }
    807             }
    808 
    809             if (result != null && result.exception != null) {
    810                 mFooters.add(new MessageFooter(
    811                         3, R.drawable.ic_dialog_alert, getString(R.string.query_error)));
    812             }
    813 
    814             if (isEmpty()) {
    815                 mEmptyView.setVisibility(View.VISIBLE);
    816             } else {
    817                 mEmptyView.setVisibility(View.GONE);
    818             }
    819 
    820             notifyDataSetChanged();
    821         }
    822 
    823         @Override
    824         public View getView(int position, View convertView, ViewGroup parent) {
    825             if (position < mCursorCount) {
    826                 return getDocumentView(position, convertView, parent);
    827             } else {
    828                 position -= mCursorCount;
    829                 convertView = mFooters.get(position).getView(convertView, parent);
    830                 // Only the view itself is disabled; contents inside shouldn't
    831                 // be dimmed.
    832                 convertView.setEnabled(false);
    833                 return convertView;
    834             }
    835         }
    836 
    837         private View getDocumentView(int position, View convertView, ViewGroup parent) {
    838             final Context context = parent.getContext();
    839             final State state = getDisplayState(DirectoryFragment.this);
    840 
    841             final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);
    842 
    843             final RootsCache roots = DocumentsApplication.getRootsCache(context);
    844             final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
    845                     context, mThumbSize);
    846 
    847             if (convertView == null) {
    848                 final LayoutInflater inflater = LayoutInflater.from(context);
    849                 if (state.derivedMode == MODE_LIST) {
    850                     convertView = inflater.inflate(R.layout.item_doc_list, parent, false);
    851                 } else if (state.derivedMode == MODE_GRID) {
    852                     convertView = inflater.inflate(R.layout.item_doc_grid, parent, false);
    853                 } else {
    854                     throw new IllegalStateException();
    855                 }
    856             }
    857 
    858             final Cursor cursor = getItem(position);
    859 
    860             final String docAuthority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
    861             final String docRootId = getCursorString(cursor, RootCursorWrapper.COLUMN_ROOT_ID);
    862             final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
    863             final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
    864             final String docDisplayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
    865             final long docLastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
    866             final int docIcon = getCursorInt(cursor, Document.COLUMN_ICON);
    867             final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
    868             final String docSummary = getCursorString(cursor, Document.COLUMN_SUMMARY);
    869             final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE);
    870 
    871             final View line1 = convertView.findViewById(R.id.line1);
    872             final View line2 = convertView.findViewById(R.id.line2);
    873 
    874             final ImageView iconMime = (ImageView) convertView.findViewById(R.id.icon_mime);
    875             final ImageView iconThumb = (ImageView) convertView.findViewById(R.id.icon_thumb);
    876             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
    877             final ImageView icon1 = (ImageView) convertView.findViewById(android.R.id.icon1);
    878             final ImageView icon2 = (ImageView) convertView.findViewById(android.R.id.icon2);
    879             final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
    880             final TextView date = (TextView) convertView.findViewById(R.id.date);
    881             final TextView size = (TextView) convertView.findViewById(R.id.size);
    882 
    883             final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) iconThumb.getTag();
    884             if (oldTask != null) {
    885                 oldTask.preempt();
    886                 iconThumb.setTag(null);
    887             }
    888 
    889             iconMime.animate().cancel();
    890             iconThumb.animate().cancel();
    891 
    892             final boolean supportsThumbnail = (docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
    893             final boolean allowThumbnail = (state.derivedMode == MODE_GRID)
    894                     || MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, docMimeType);
    895             final boolean showThumbnail = supportsThumbnail && allowThumbnail && !mSvelteRecents;
    896 
    897             final boolean enabled = isDocumentEnabled(docMimeType, docFlags);
    898             final float iconAlpha = (state.derivedMode == MODE_LIST && !enabled) ? 0.5f : 1f;
    899 
    900             boolean cacheHit = false;
    901             if (showThumbnail) {
    902                 final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId);
    903                 final Bitmap cachedResult = thumbs.get(uri);
    904                 if (cachedResult != null) {
    905                     iconThumb.setImageBitmap(cachedResult);
    906                     cacheHit = true;
    907                 } else {
    908                     iconThumb.setImageDrawable(null);
    909                     final ThumbnailAsyncTask task = new ThumbnailAsyncTask(
    910                             uri, iconMime, iconThumb, mThumbSize, iconAlpha);
    911                     iconThumb.setTag(task);
    912                     ProviderExecutor.forAuthority(docAuthority).execute(task);
    913                 }
    914             }
    915 
    916             // Always throw MIME icon into place, even when a thumbnail is being
    917             // loaded in background.
    918             if (cacheHit) {
    919                 iconMime.setAlpha(0f);
    920                 iconMime.setImageDrawable(null);
    921                 iconThumb.setAlpha(1f);
    922             } else {
    923                 iconMime.setAlpha(1f);
    924                 iconThumb.setAlpha(0f);
    925                 iconThumb.setImageDrawable(null);
    926                 if (docIcon != 0) {
    927                     iconMime.setImageDrawable(
    928                             IconUtils.loadPackageIcon(context, docAuthority, docIcon));
    929                 } else {
    930                     iconMime.setImageDrawable(IconUtils.loadMimeIcon(
    931                             context, docMimeType, docAuthority, docId, state.derivedMode));
    932                 }
    933             }
    934 
    935             boolean hasLine1 = false;
    936             boolean hasLine2 = false;
    937 
    938             final boolean hideTitle = (state.derivedMode == MODE_GRID) && mHideGridTitles;
    939             if (!hideTitle) {
    940                 title.setText(docDisplayName);
    941                 hasLine1 = true;
    942             }
    943 
    944             Drawable iconDrawable = null;
    945             if (mType == TYPE_RECENT_OPEN) {
    946                 // We've already had to enumerate roots before any results can
    947                 // be shown, so this will never block.
    948                 final RootInfo root = roots.getRootBlocking(docAuthority, docRootId);
    949                 if (state.derivedMode == MODE_GRID) {
    950                     iconDrawable = root.loadGridIcon(context);
    951                 } else {
    952                     iconDrawable = root.loadIcon(context);
    953                 }
    954 
    955                 if (summary != null) {
    956                     final boolean alwaysShowSummary = getResources()
    957                             .getBoolean(R.bool.always_show_summary);
    958                     if (alwaysShowSummary) {
    959                         summary.setText(root.getDirectoryString());
    960                         summary.setVisibility(View.VISIBLE);
    961                         hasLine2 = true;
    962                     } else {
    963                         if (iconDrawable != null && roots.isIconUniqueBlocking(root)) {
    964                             // No summary needed if icon speaks for itself
    965                             summary.setVisibility(View.INVISIBLE);
    966                         } else {
    967                             summary.setText(root.getDirectoryString());
    968                             summary.setVisibility(View.VISIBLE);
    969                             summary.setTextAlignment(TextView.TEXT_ALIGNMENT_TEXT_END);
    970                             hasLine2 = true;
    971                         }
    972                     }
    973                 }
    974             } else {
    975                 // Directories showing thumbnails in grid mode get a little icon
    976                 // hint to remind user they're a directory.
    977                 if (Document.MIME_TYPE_DIR.equals(docMimeType) && state.derivedMode == MODE_GRID
    978                         && showThumbnail) {
    979                     iconDrawable = IconUtils.applyTintAttr(context, R.drawable.ic_doc_folder,
    980                             android.R.attr.textColorPrimaryInverse);
    981                 }
    982 
    983                 if (summary != null) {
    984                     if (docSummary != null) {
    985                         summary.setText(docSummary);
    986                         summary.setVisibility(View.VISIBLE);
    987                         hasLine2 = true;
    988                     } else {
    989                         summary.setVisibility(View.INVISIBLE);
    990                     }
    991                 }
    992             }
    993 
    994             if (icon1 != null) icon1.setVisibility(View.GONE);
    995             if (icon2 != null) icon2.setVisibility(View.GONE);
    996 
    997             if (iconDrawable != null) {
    998                 if (hasLine1) {
    999                     icon1.setVisibility(View.VISIBLE);
   1000                     icon1.setImageDrawable(iconDrawable);
   1001                 } else {
   1002                     icon2.setVisibility(View.VISIBLE);
   1003                     icon2.setImageDrawable(iconDrawable);
   1004                 }
   1005             }
   1006 
   1007             if (docLastModified == -1) {
   1008                 date.setText(null);
   1009             } else {
   1010                 date.setText(formatTime(context, docLastModified));
   1011                 hasLine2 = true;
   1012             }
   1013 
   1014             if (state.showSize) {
   1015                 size.setVisibility(View.VISIBLE);
   1016                 if (Document.MIME_TYPE_DIR.equals(docMimeType) || docSize == -1) {
   1017                     size.setText(null);
   1018                 } else {
   1019                     size.setText(Formatter.formatFileSize(context, docSize));
   1020                     hasLine2 = true;
   1021                 }
   1022             } else {
   1023                 size.setVisibility(View.GONE);
   1024             }
   1025 
   1026             if (line1 != null) {
   1027                 line1.setVisibility(hasLine1 ? View.VISIBLE : View.GONE);
   1028             }
   1029             if (line2 != null) {
   1030                 line2.setVisibility(hasLine2 ? View.VISIBLE : View.GONE);
   1031             }
   1032 
   1033             setEnabledRecursive(convertView, enabled);
   1034 
   1035             iconMime.setAlpha(iconAlpha);
   1036             iconThumb.setAlpha(iconAlpha);
   1037             if (icon1 != null) icon1.setAlpha(iconAlpha);
   1038             if (icon2 != null) icon2.setAlpha(iconAlpha);
   1039 
   1040             return convertView;
   1041         }
   1042 
   1043         @Override
   1044         public int getCount() {
   1045             return mCursorCount + mFooters.size();
   1046         }
   1047 
   1048         @Override
   1049         public Cursor getItem(int position) {
   1050             if (position < mCursorCount) {
   1051                 mCursor.moveToPosition(position);
   1052                 return mCursor;
   1053             } else {
   1054                 return null;
   1055             }
   1056         }
   1057 
   1058         @Override
   1059         public long getItemId(int position) {
   1060             return position;
   1061         }
   1062 
   1063         @Override
   1064         public int getViewTypeCount() {
   1065             return 4;
   1066         }
   1067 
   1068         @Override
   1069         public int getItemViewType(int position) {
   1070             if (position < mCursorCount) {
   1071                 return 0;
   1072             } else {
   1073                 position -= mCursorCount;
   1074                 return mFooters.get(position).getItemViewType();
   1075             }
   1076         }
   1077     }
   1078 
   1079     private static class ThumbnailAsyncTask extends AsyncTask<Uri, Void, Bitmap>
   1080             implements Preemptable {
   1081         private final Uri mUri;
   1082         private final ImageView mIconMime;
   1083         private final ImageView mIconThumb;
   1084         private final Point mThumbSize;
   1085         private final float mTargetAlpha;
   1086         private final CancellationSignal mSignal;
   1087 
   1088         public ThumbnailAsyncTask(Uri uri, ImageView iconMime, ImageView iconThumb, Point thumbSize,
   1089                 float targetAlpha) {
   1090             mUri = uri;
   1091             mIconMime = iconMime;
   1092             mIconThumb = iconThumb;
   1093             mThumbSize = thumbSize;
   1094             mTargetAlpha = targetAlpha;
   1095             mSignal = new CancellationSignal();
   1096         }
   1097 
   1098         @Override
   1099         public void preempt() {
   1100             cancel(false);
   1101             mSignal.cancel();
   1102         }
   1103 
   1104         @Override
   1105         protected Bitmap doInBackground(Uri... params) {
   1106             if (isCancelled()) return null;
   1107 
   1108             final Context context = mIconThumb.getContext();
   1109             final ContentResolver resolver = context.getContentResolver();
   1110 
   1111             ContentProviderClient client = null;
   1112             Bitmap result = null;
   1113             try {
   1114                 client = DocumentsApplication.acquireUnstableProviderOrThrow(
   1115                         resolver, mUri.getAuthority());
   1116                 result = DocumentsContract.getDocumentThumbnail(client, mUri, mThumbSize, mSignal);
   1117                 if (result != null) {
   1118                     final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
   1119                             context, mThumbSize);
   1120                     thumbs.put(mUri, result);
   1121                 }
   1122             } catch (Exception e) {
   1123                 if (!(e instanceof OperationCanceledException)) {
   1124                     Log.w(TAG, "Failed to load thumbnail for " + mUri + ": " + e);
   1125                 }
   1126             } finally {
   1127                 ContentProviderClient.releaseQuietly(client);
   1128             }
   1129             return result;
   1130         }
   1131 
   1132         @Override
   1133         protected void onPostExecute(Bitmap result) {
   1134             if (mIconThumb.getTag() == this && result != null) {
   1135                 mIconThumb.setTag(null);
   1136                 mIconThumb.setImageBitmap(result);
   1137 
   1138                 mIconMime.setAlpha(mTargetAlpha);
   1139                 mIconMime.animate().alpha(0f).start();
   1140                 mIconThumb.setAlpha(0f);
   1141                 mIconThumb.animate().alpha(mTargetAlpha).start();
   1142             }
   1143         }
   1144     }
   1145 
   1146     private static String formatTime(Context context, long when) {
   1147         // TODO: DateUtils should make this easier
   1148         Time then = new Time();
   1149         then.set(when);
   1150         Time now = new Time();
   1151         now.setToNow();
   1152 
   1153         int flags = DateUtils.FORMAT_NO_NOON | DateUtils.FORMAT_NO_MIDNIGHT
   1154                 | DateUtils.FORMAT_ABBREV_ALL;
   1155 
   1156         if (then.year != now.year) {
   1157             flags |= DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE;
   1158         } else if (then.yearDay != now.yearDay) {
   1159             flags |= DateUtils.FORMAT_SHOW_DATE;
   1160         } else {
   1161             flags |= DateUtils.FORMAT_SHOW_TIME;
   1162         }
   1163 
   1164         return DateUtils.formatDateTime(context, when, flags);
   1165     }
   1166 
   1167     private String findCommonMimeType(List<String> mimeTypes) {
   1168         String[] commonType = mimeTypes.get(0).split("/");
   1169         if (commonType.length != 2) {
   1170             return "*/*";
   1171         }
   1172 
   1173         for (int i = 1; i < mimeTypes.size(); i++) {
   1174             String[] type = mimeTypes.get(i).split("/");
   1175             if (type.length != 2) continue;
   1176 
   1177             if (!commonType[1].equals(type[1])) {
   1178                 commonType[1] = "*";
   1179             }
   1180 
   1181             if (!commonType[0].equals(type[0])) {
   1182                 commonType[0] = "*";
   1183                 commonType[1] = "*";
   1184                 break;
   1185             }
   1186         }
   1187 
   1188         return commonType[0] + "/" + commonType[1];
   1189     }
   1190 
   1191     private void setEnabledRecursive(View v, boolean enabled) {
   1192         if (v == null) return;
   1193         if (v.isEnabled() == enabled) return;
   1194         v.setEnabled(enabled);
   1195 
   1196         if (v instanceof ViewGroup) {
   1197             final ViewGroup vg = (ViewGroup) v;
   1198             for (int i = vg.getChildCount() - 1; i >= 0; i--) {
   1199                 setEnabledRecursive(vg.getChildAt(i), enabled);
   1200             }
   1201         }
   1202     }
   1203 
   1204     private boolean isDocumentEnabled(String docMimeType, int docFlags) {
   1205         final State state = getDisplayState(DirectoryFragment.this);
   1206 
   1207         // Directories are always enabled
   1208         if (Document.MIME_TYPE_DIR.equals(docMimeType)) {
   1209             return true;
   1210         }
   1211 
   1212         // Read-only files are disabled when creating
   1213         if (state.action == ACTION_CREATE && (docFlags & Document.FLAG_SUPPORTS_WRITE) == 0) {
   1214             return false;
   1215         }
   1216 
   1217         return MimePredicate.mimeMatches(state.acceptMimes, docMimeType);
   1218     }
   1219 }
   1220