Disabled states, more UX work, bug fixes.

Fix drawable state to correctly show dimmed disabled state.  Update
disabled state for all children to grey out text.

Block multi-selection of documents not matching MIME filter.  Load
thumbnails in parallel.  Show thumbnails in list mode based on MIME
type to match spec.

Give each footer a unique view type to avoid recycler crashes.

Show breadcrumb icons in recent create paths.  Fix timestamp bug when
querying/updating recent paths.

Make ContentProviderClient.closeQuietly() really be quiet.

Bug: 10668364, 10510022, 10668701, 10534224, 10667726
Change-Id: I3c705412fb211519f15ad41a273a7533b878e9e5
diff --git a/src/com/android/documentsui/DirectoryFragment.java b/src/com/android/documentsui/DirectoryFragment.java
index a13beba..f9ac3f3 100644
--- a/src/com/android/documentsui/DirectoryFragment.java
+++ b/src/com/android/documentsui/DirectoryFragment.java
@@ -106,6 +106,11 @@
     private static final String EXTRA_DOC = "doc";
     private static final String EXTRA_QUERY = "query";
 
+    /**
+     * MIME types that should always show thumbnails in list mode.
+     */
+    private static final String[] LIST_THUMBNAIL_MIMES = new String[] { "image/*", "video/*" };
+
     private static AtomicInteger sLoaderId = new AtomicInteger(4000);
 
     private final int mLoaderId = sLoaderId.incrementAndGet();
@@ -294,9 +299,11 @@
         @Override
         public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
             final Cursor cursor = mAdapter.getItem(position);
-            final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
-            if (mFilter.apply(doc)) {
-                ((DocumentsActivity) getActivity()).onDocumentPicked(doc);
+            if (cursor != null) {
+                final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
+                if (mFilter.apply(doc)) {
+                    ((DocumentsActivity) getActivity()).onDocumentPicked(doc);
+                }
             }
         }
     };
@@ -367,10 +374,20 @@
         public void onItemCheckedStateChanged(
                 ActionMode mode, int position, long id, boolean checked) {
             if (checked) {
-                // Directories cannot be checked
+                // Directories and footer items cannot be checked
+                boolean valid = false;
+
                 final Cursor cursor = mAdapter.getItem(position);
-                final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
-                if (Document.MIME_TYPE_DIR.equals(docMimeType)) {
+                if (cursor != null) {
+                    final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+
+                    // Only valid if non-directory matches filter
+                    final State state = getDisplayState(DirectoryFragment.this);
+                    valid = !Document.MIME_TYPE_DIR.equals(docMimeType)
+                            && MimePredicate.mimeMatches(state.acceptMimes, docMimeType);
+                }
+
+                if (!valid) {
                     mCurrentView.setItemChecked(position, false);
                 }
             }
@@ -441,11 +458,25 @@
         return ((DocumentsActivity) fragment.getActivity()).getDisplayState();
     }
 
-    private interface Footer {
-        public View getView(View convertView, ViewGroup parent);
+    private static abstract class Footer {
+        private final int mItemViewType;
+
+        public Footer(int itemViewType) {
+            mItemViewType = itemViewType;
+        }
+
+        public abstract View getView(View convertView, ViewGroup parent);
+
+        public int getItemViewType() {
+            return mItemViewType;
+        }
     }
 
-    private static class LoadingFooter implements Footer {
+    private static class LoadingFooter extends Footer {
+        public LoadingFooter() {
+            super(1);
+        }
+
         @Override
         public View getView(View convertView, ViewGroup parent) {
             final Context context = parent.getContext();
@@ -457,11 +488,12 @@
         }
     }
 
-    private class MessageFooter implements Footer {
+    private class MessageFooter extends Footer {
         private final int mIcon;
         private final String mMessage;
 
-        public MessageFooter(int icon, String message) {
+        public MessageFooter(int itemViewType, int icon, String message) {
+            super(itemViewType);
             mIcon = icon;
             mMessage = message;
         }
@@ -506,11 +538,11 @@
             if (extras != null) {
                 final String info = extras.getString(DocumentsContract.EXTRA_INFO);
                 if (info != null) {
-                    mFooters.add(new MessageFooter(R.drawable.ic_dialog_alert, info));
+                    mFooters.add(new MessageFooter(2, R.drawable.ic_dialog_alert, info));
                 }
                 final String error = extras.getString(DocumentsContract.EXTRA_ERROR);
                 if (error != null) {
-                    mFooters.add(new MessageFooter(R.drawable.ic_dialog_alert, error));
+                    mFooters.add(new MessageFooter(3, R.drawable.ic_dialog_alert, error));
                 }
                 if (extras.getBoolean(DocumentsContract.EXTRA_LOADING, false)) {
                     mFooters.add(new LoadingFooter());
@@ -532,7 +564,11 @@
                 return getDocumentView(position, convertView, parent);
             } else {
                 position -= mCursorCount;
-                return mFooters.get(position).getView(convertView, parent);
+                convertView = mFooters.get(position).getView(convertView, parent);
+                // Only the view itself is disabled; contents inside shouldn't
+                // be dimmed.
+                convertView.setEnabled(false);
+                return convertView;
             }
         }
 
@@ -581,7 +617,11 @@
                 oldTask.cancel(false);
             }
 
-            if ((docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0) {
+            final boolean supportsThumbnail = (docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
+            final boolean allowThumbnail = (state.mode == MODE_GRID)
+                    || MimePredicate.mimeMatches(LIST_THUMBNAIL_MIMES, docMimeType);
+
+            if (supportsThumbnail && allowThumbnail) {
                 final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId);
                 final Bitmap cachedResult = thumbs.get(uri);
                 if (cachedResult != null) {
@@ -590,7 +630,7 @@
                     final ThumbnailAsyncTask task = new ThumbnailAsyncTask(icon, mThumbSize);
                     icon.setImageBitmap(null);
                     icon.setTag(task);
-                    task.execute(uri);
+                    task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, uri);
                 }
             } else if (docIcon != 0) {
                 icon.setImageDrawable(IconUtils.loadPackageIcon(context, docAuthority, docIcon));
@@ -642,6 +682,18 @@
 
             line2.setVisibility(hasLine2 ? View.VISIBLE : View.GONE);
 
+            final boolean enabled = Document.MIME_TYPE_DIR.equals(docMimeType)
+                    || MimePredicate.mimeMatches(state.acceptMimes, docMimeType);
+            if (enabled) {
+                setEnabledRecursive(convertView, true);
+                icon.setAlpha(1f);
+                icon1.setAlpha(1f);
+            } else {
+                setEnabledRecursive(convertView, false);
+                icon.setAlpha(0.5f);
+                icon1.setAlpha(0.5f);
+            }
+
             return convertView;
         }
 
@@ -666,23 +718,19 @@
         }
 
         @Override
+        public int getViewTypeCount() {
+            return 4;
+        }
+
+        @Override
         public int getItemViewType(int position) {
             if (position < mCursorCount) {
                 return 0;
             } else {
-                return IGNORE_ITEM_VIEW_TYPE;
+                position -= mCursorCount;
+                return mFooters.get(position).getItemViewType();
             }
         }
-
-        @Override
-        public boolean areAllItemsEnabled() {
-            return false;
-        }
-
-        @Override
-        public boolean isEnabled(int position) {
-            return position < mCursorCount;
-        }
     }
 
     private static class ThumbnailAsyncTask extends AsyncTask<Uri, Void, Bitmap> {
@@ -772,4 +820,16 @@
 
         return commonType[0] + "/" + commonType[1];
     }
+
+    private void setEnabledRecursive(View v, boolean enabled) {
+        if (v.isEnabled() == enabled) return;
+        v.setEnabled(enabled);
+
+        if (v instanceof ViewGroup) {
+            final ViewGroup vg = (ViewGroup) v;
+            for (int i = vg.getChildCount() - 1; i >= 0; i--) {
+                setEnabledRecursive(vg.getChildAt(i), enabled);
+            }
+        }
+    }
 }