am ff44ed58: Merge "Disabled states, more UX work, bug fixes." into klp-dev

* commit 'ff44ed58a1e38ff830e74b0d73549ff01725852d':
  Disabled states, more UX work, bug fixes.
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index e6d9b24..e83c727 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -344,7 +344,10 @@
     /** {@hide} */
     public static void closeQuietly(ContentProviderClient client) {
         if (client != null) {
-            client.release();
+            try {
+                client.release();
+            } catch (Exception ignored) {
+            }
         }
     }
 }
diff --git a/packages/DocumentsUI/res/drawable/item_background.xml b/packages/DocumentsUI/res/drawable/item_background.xml
index 4fb32fc..7447aa8 100644
--- a/packages/DocumentsUI/res/drawable/item_background.xml
+++ b/packages/DocumentsUI/res/drawable/item_background.xml
@@ -15,12 +15,20 @@
 -->
 
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_activated="true"                               android:state_pressed="true" android:drawable="@*android:drawable/list_activated_holo" />
-    <item android:state_activated="true"                                                            android:drawable="@*android:drawable/list_activated_holo" />
-    <item android:state_focused="true"   android:state_enabled="false" android:state_pressed="true" android:drawable="@*android:drawable/list_selector_disabled_holo_light" />
-    <item android:state_focused="true"   android:state_enabled="false"                              android:drawable="@*android:drawable/list_selector_disabled_holo_light" />
-    <item android:state_focused="true"                                 android:state_pressed="true" android:drawable="@*android:drawable/list_selector_background_transition_holo_light" />
-    <item android:state_focused="false"                                android:state_pressed="true" android:drawable="@*android:drawable/list_selector_background_transition_holo_light" />
-    <item android:state_focused="true"                                                              android:drawable="@*android:drawable/list_focused_holo" />
-    <item                                                                                           android:drawable="@android:color/transparent" />
+
+    <item android:state_enabled="false" android:state_selected="true" android:drawable="@*android:drawable/list_selector_disabled_holo_light" />
+    <item android:state_enabled="false" android:state_focused="true"  android:drawable="@*android:drawable/list_selector_disabled_holo_light" />
+    <item android:state_enabled="false" android:state_pressed="true"  android:drawable="@*android:drawable/list_selector_disabled_holo_light" />
+
+    <item android:state_activated="true" android:state_pressed="true" android:drawable="@*android:drawable/list_activated_holo" />
+    <item android:state_activated="true" android:drawable="@*android:drawable/list_activated_holo" />
+
+    <item android:state_focused="true" android:drawable="@*android:drawable/list_focused_holo" />
+    <item android:state_selected="true" android:drawable="@*android:drawable/list_focused_holo" />
+
+    <item android:state_pressed="true" android:state_focused="true" android:drawable="@*android:drawable/list_selector_background_transition_holo_light" />
+    <item android:state_pressed="true" android:drawable="@*android:drawable/list_selector_background_transition_holo_light" />
+
+    <item android:drawable="@android:color/transparent" />
+
 </selector>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index a13beba..f9ac3f3 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/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);
+            }
+        }
+    }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
index 9391ca9..3642478 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
@@ -26,10 +26,14 @@
 import android.content.Context;
 import android.content.Loader;
 import android.database.Cursor;
+import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.text.Spannable;
+import android.text.SpannableStringBuilder;
 import android.text.TextUtils.TruncateAt;
+import android.text.style.ImageSpan;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -181,20 +185,29 @@
 
             final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
+            final View line2 = convertView.findViewById(R.id.line2);
 
             final DocumentStack stack = getItem(position);
             icon.setImageDrawable(stack.root.loadIcon(context));
 
-            final StringBuilder builder = new StringBuilder();
-            for (int i = stack.size() - 1; i >= 0; i--) {
+            final Drawable crumb = context.getResources()
+                    .getDrawable(R.drawable.ic_breadcrumb_arrow);
+            crumb.setBounds(0, 0, crumb.getIntrinsicWidth(), crumb.getIntrinsicHeight());
+
+            final SpannableStringBuilder builder = new SpannableStringBuilder();
+            builder.append(stack.root.title);
+            appendDrawable(builder, crumb);
+            for (int i = stack.size() - 2; i >= 0; i--) {
                 builder.append(stack.get(i).displayName);
                 if (i > 0) {
-                    builder.append(" \u232a ");
+                    appendDrawable(builder, crumb);
                 }
             }
-            title.setText(builder.toString());
+            title.setText(builder);
             title.setEllipsize(TruncateAt.MIDDLE);
 
+            line2.setVisibility(View.GONE);
+
             return convertView;
         }
 
@@ -213,4 +226,10 @@
             return getItem(position).hashCode();
         }
     }
+
+    private static void appendDrawable(SpannableStringBuilder b, Drawable d) {
+        final int length = b.length();
+        b.append("\u232a");
+        b.setSpan(new ImageSpan(d), length, b.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+    }
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
index df7ed4a..1fe5d54 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
@@ -149,9 +149,9 @@
         final SQLiteDatabase db = mHelper.getReadableDatabase();
         switch (sMatcher.match(uri)) {
             case URI_RECENT:
-                return db.query(TABLE_RECENT, projection,
-                        RecentColumns.TIMESTAMP + "<" + MAX_HISTORY_IN_MILLIS, null, null, null,
-                        null);
+                final long cutoff = System.currentTimeMillis() - MAX_HISTORY_IN_MILLIS;
+                return db.query(TABLE_RECENT, projection, RecentColumns.TIMESTAMP + ">" + cutoff,
+                        null, null, null, null);
             case URI_STATE:
                 final String authority = uri.getPathSegments().get(1);
                 final String rootId = uri.getPathSegments().get(2);
@@ -180,8 +180,8 @@
             case URI_RECENT:
                 values.put(RecentColumns.TIMESTAMP, System.currentTimeMillis());
                 db.insert(TABLE_RECENT, null, values);
-                db.delete(
-                        TABLE_RECENT, RecentColumns.TIMESTAMP + ">" + MAX_HISTORY_IN_MILLIS, null);
+                final long cutoff = System.currentTimeMillis() - MAX_HISTORY_IN_MILLIS;
+                db.delete(TABLE_RECENT, RecentColumns.TIMESTAMP + "<" + cutoff, null);
                 return uri;
             case URI_STATE:
                 final String authority = uri.getPathSegments().get(1);
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/RootsCacheTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/RootsCacheTest.java
index f53e60d..cdb6b33 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/RootsCacheTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/RootsCacheTest.java
@@ -30,7 +30,7 @@
 
     private static RootInfo buildForMimeTypes(String... mimeTypes) {
         final RootInfo root = new RootInfo();
-        root.mimeTypes = mimeTypes;
+        root.derivedMimeTypes = mimeTypes;
         return root;
     }
 
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java
index 872974f..93db592 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java
@@ -125,6 +125,9 @@
                 includeFile(result, "_networkfile1");
                 includeFile(result, "_networkfile2");
                 includeFile(result, "_networkfile3");
+                includeFile(result, "_networkfile4");
+                includeFile(result, "_networkfile5");
+                includeFile(result, "_networkfile6");
                 return true;
             } else {
                 return false;
@@ -162,6 +165,8 @@
         includeFile(result, MY_DOC_NULL);
         includeFile(result, "localfile1");
         includeFile(result, "localfile2");
+        includeFile(result, "localfile3");
+        includeFile(result, "localfile4");
 
         synchronized (this) {
             // Try picking up an existing network fetch