am af4d039f: Merge "More UX work for thumbnails, search, management." into klp-dev

* commit 'af4d039fb3a45d4a74683a69ee48071b8338a13d':
  More UX work for thumbnails, search, management.
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 91b3b48..43120c4 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -459,6 +459,7 @@
     private static final String PATH_SEARCH = "search";
 
     private static final String PARAM_QUERY = "query";
+    private static final String PARAM_MANAGE = "manage";
 
     /**
      * Build Uri representing the roots of a document provider. When queried, a
@@ -583,6 +584,16 @@
         return searchDocumentsUri.getQueryParameter(PARAM_QUERY);
     }
 
+    /** {@hide} */
+    public static Uri setManageMode(Uri uri) {
+        return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build();
+    }
+
+    /** {@hide} */
+    public static boolean isManageMode(Uri uri) {
+        return uri.getBooleanQueryParameter(PARAM_MANAGE, false);
+    }
+
     /**
      * Return list of all documents that the calling package has "open." These
      * are Uris matching {@link DocumentsContract} to which persistent
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 1b0fc4d..d801827 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -167,6 +167,14 @@
             String parentDocumentId, String[] projection, String sortOrder)
             throws FileNotFoundException;
 
+    /** {@hide} */
+    @SuppressWarnings("unused")
+    public Cursor queryChildDocumentsForManage(
+            String parentDocumentId, String[] projection, String sortOrder)
+            throws FileNotFoundException {
+        throw new UnsupportedOperationException("Manage not supported");
+    }
+
     /**
      * Return documents that that match the given query, starting the search at
      * the given directory.
@@ -262,7 +270,12 @@
                 case MATCH_DOCUMENT:
                     return queryDocument(getDocumentId(uri), projection);
                 case MATCH_CHILDREN:
-                    return queryChildDocuments(getDocumentId(uri), projection, sortOrder);
+                    if (DocumentsContract.isManageMode(uri)) {
+                        return queryChildDocumentsForManage(
+                                getDocumentId(uri), projection, sortOrder);
+                    } else {
+                        return queryChildDocuments(getDocumentId(uri), projection, sortOrder);
+                    }
                 case MATCH_SEARCH:
                     return querySearchDocuments(
                             getDocumentId(uri), getSearchDocumentsQuery(uri), projection);
diff --git a/packages/DocumentsUI/res/layout/item_doc_grid.xml b/packages/DocumentsUI/res/layout/item_doc_grid.xml
index 8d1fc9a..8b37d21 100644
--- a/packages/DocumentsUI/res/layout/item_doc_grid.xml
+++ b/packages/DocumentsUI/res/layout/item_doc_grid.xml
@@ -32,12 +32,26 @@
             android:layout_weight="1"
             android:background="#fff">
 
-            <ImageView
+            <FrameLayout
                 android:id="@android:id/icon"
                 android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:scaleType="centerInside"
-                android:contentDescription="@null" />
+                android:layout_height="match_parent">
+
+                <ImageView
+                    android:id="@+id/icon_mime"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:scaleType="centerInside"
+                    android:contentDescription="@null" />
+
+                <ImageView
+                    android:id="@+id/icon_thumb"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    android:scaleType="centerCrop"
+                    android:contentDescription="@null" />
+
+            </FrameLayout>
 
             <ImageView
                 android:layout_width="match_parent"
diff --git a/packages/DocumentsUI/res/layout/item_doc_list.xml b/packages/DocumentsUI/res/layout/item_doc_list.xml
index 8372eed..9958e44 100644
--- a/packages/DocumentsUI/res/layout/item_doc_list.xml
+++ b/packages/DocumentsUI/res/layout/item_doc_list.xml
@@ -25,15 +25,29 @@
     android:paddingBottom="8dip"
     android:orientation="horizontal">
 
-    <ImageView
+    <FrameLayout
         android:id="@android:id/icon"
         android:layout_width="@dimen/icon_size"
         android:layout_height="@dimen/icon_size"
         android:layout_marginStart="12dp"
         android:layout_marginEnd="20dp"
-        android:layout_gravity="center_vertical"
-        android:scaleType="centerInside"
-        android:contentDescription="@null" />
+        android:layout_gravity="center_vertical">
+
+        <ImageView
+            android:id="@+id/icon_mime"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scaleType="centerInside"
+            android:contentDescription="@null" />
+
+        <ImageView
+            android:id="@+id/icon_thumb"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:scaleType="centerCrop"
+            android:contentDescription="@null" />
+
+    </FrameLayout>
 
     <LinearLayout
         android:layout_width="0dip"
diff --git a/packages/DocumentsUI/res/layout/item_root_header.xml b/packages/DocumentsUI/res/layout/item_root_header.xml
index 127b254..7b6cd76 100644
--- a/packages/DocumentsUI/res/layout/item_root_header.xml
+++ b/packages/DocumentsUI/res/layout/item_root_header.xml
@@ -14,6 +14,13 @@
      limitations under the License.
 -->
 
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@android:id/title"
-    style="?android:attr/listSeparatorTextViewStyle" />
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:paddingTop="12dp">
+
+    <TextView
+        android:id="@android:id/title"
+        style="?android:attr/listSeparatorTextViewStyle" />
+
+</FrameLayout>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 45f028d..13a84f4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -38,9 +38,11 @@
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.Point;
+import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
 import android.text.format.DateUtils;
@@ -56,6 +58,7 @@
 import android.view.ViewGroup;
 import android.widget.AbsListView;
 import android.widget.AbsListView.MultiChoiceModeListener;
+import android.widget.AbsListView.RecyclerListener;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.BaseAdapter;
@@ -162,10 +165,12 @@
         mListView = (ListView) view.findViewById(R.id.list);
         mListView.setOnItemClickListener(mItemListener);
         mListView.setMultiChoiceModeListener(mMultiListener);
+        mListView.setRecyclerListener(mRecycleListener);
 
         mGridView = (GridView) view.findViewById(R.id.grid);
         mGridView.setOnItemClickListener(mItemListener);
         mGridView.setMultiChoiceModeListener(mMultiListener);
+        mGridView.setRecyclerListener(mRecycleListener);
 
         return view;
     }
@@ -192,13 +197,19 @@
                     case TYPE_NORMAL:
                         contentsUri = DocumentsContract.buildChildDocumentsUri(
                                 doc.authority, doc.documentId);
+                        if (state.action == ACTION_MANAGE) {
+                            contentsUri = DocumentsContract.setManageMode(contentsUri);
+                        }
                         return new DirectoryLoader(
-                                context, root, doc, contentsUri, state.userSortOrder);
+                                context, mType, root, doc, contentsUri, state.userSortOrder);
                     case TYPE_SEARCH:
                         contentsUri = DocumentsContract.buildSearchDocumentsUri(
                                 doc.authority, doc.documentId, query);
+                        if (state.action == ACTION_MANAGE) {
+                            contentsUri = DocumentsContract.setManageMode(contentsUri);
+                        }
                         return new DirectoryLoader(
-                                context, root, doc, contentsUri, state.userSortOrder);
+                                context, mType, root, doc, contentsUri, state.userSortOrder);
                     case TYPE_RECENT_OPEN:
                         final RootsCache roots = DocumentsApplication.getRootsCache(context);
                         final List<RootInfo> matchingRoots = roots.getMatchingRoots(state);
@@ -425,6 +436,20 @@
         }
     };
 
+    private RecyclerListener mRecycleListener = new RecyclerListener() {
+        @Override
+        public void onMovedToScrapHeap(View view) {
+            final ImageView iconThumb = (ImageView) view.findViewById(R.id.icon_thumb);
+            if (iconThumb != null) {
+                final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) iconThumb.getTag();
+                if (oldTask != null) {
+                    oldTask.reallyCancel();
+                    iconThumb.setTag(null);
+                }
+            }
+        }
+    };
+
     private void onShareDocuments(List<DocumentInfo> docs) {
         Intent intent;
         if (docs.size() == 1) {
@@ -632,7 +657,9 @@
             final String docSummary = getCursorString(cursor, Document.COLUMN_SUMMARY);
             final long docSize = getCursorLong(cursor, Document.COLUMN_SIZE);
 
-            final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
+            final View icon = convertView.findViewById(android.R.id.icon);
+            final ImageView iconMime = (ImageView) convertView.findViewById(R.id.icon_mime);
+            final ImageView iconThumb = (ImageView) convertView.findViewById(R.id.icon_thumb);
             final TextView title = (TextView) convertView.findViewById(android.R.id.title);
             final View line2 = convertView.findViewById(R.id.line2);
             final ImageView icon1 = (ImageView) convertView.findViewById(android.R.id.icon1);
@@ -640,30 +667,49 @@
             final TextView date = (TextView) convertView.findViewById(R.id.date);
             final TextView size = (TextView) convertView.findViewById(R.id.size);
 
-            final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) icon.getTag();
+            final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) iconThumb.getTag();
             if (oldTask != null) {
-                oldTask.cancel(false);
+                oldTask.reallyCancel();
+                iconThumb.setTag(null);
             }
 
+            iconMime.animate().cancel();
+            iconThumb.animate().cancel();
+
             final boolean supportsThumbnail = (docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
             final boolean allowThumbnail = (state.derivedMode == MODE_GRID)
                     || MimePredicate.mimeMatches(LIST_THUMBNAIL_MIMES, docMimeType);
 
+            boolean cacheHit = false;
             if (supportsThumbnail && allowThumbnail) {
                 final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId);
                 final Bitmap cachedResult = thumbs.get(uri);
                 if (cachedResult != null) {
-                    icon.setImageBitmap(cachedResult);
+                    iconThumb.setImageBitmap(cachedResult);
+                    cacheHit = true;
                 } else {
-                    final ThumbnailAsyncTask task = new ThumbnailAsyncTask(icon, mThumbSize);
-                    icon.setImageBitmap(null);
-                    icon.setTag(task);
-                    task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, uri);
+                    iconThumb.setImageDrawable(null);
+                    final ThumbnailAsyncTask task = new ThumbnailAsyncTask(
+                            uri, iconMime, iconThumb, mThumbSize);
+                    iconThumb.setTag(task);
+                    task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
                 }
-            } else if (docIcon != 0) {
-                icon.setImageDrawable(IconUtils.loadPackageIcon(context, docAuthority, docIcon));
+            }
+
+            // Always throw MIME icon into place, even when a thumbnail is being
+            // loaded in background.
+            if (cacheHit) {
+                iconMime.setAlpha(0f);
+                iconThumb.setAlpha(1f);
             } else {
-                icon.setImageDrawable(IconUtils.loadMimeIcon(context, docMimeType));
+                iconMime.setAlpha(1f);
+                iconThumb.setAlpha(0f);
+                if (docIcon != 0) {
+                    iconMime.setImageDrawable(
+                            IconUtils.loadPackageIcon(context, docAuthority, docIcon));
+                } else {
+                    iconMime.setImageDrawable(IconUtils.loadMimeIcon(context, docMimeType));
+                }
             }
 
             title.setText(docDisplayName);
@@ -672,12 +718,19 @@
 
             if (mType == TYPE_RECENT_OPEN) {
                 final RootInfo root = roots.getRoot(docAuthority, docRootId);
+                final Drawable iconDrawable = root.loadIcon(context);
                 icon1.setVisibility(View.VISIBLE);
-                icon1.setImageDrawable(root.loadIcon(context));
-                summary.setText(root.getDirectoryString());
-                summary.setVisibility(View.VISIBLE);
-                summary.setTextAlignment(TextView.TEXT_ALIGNMENT_TEXT_END);
-                hasLine2 = true;
+                icon1.setImageDrawable(iconDrawable);
+
+                if (iconDrawable != null && roots.isIconUnique(root)) {
+                    // No summary needed if icon speaks for itself
+                    summary.setVisibility(View.INVISIBLE);
+                } else {
+                    summary.setText(root.getDirectoryString());
+                    summary.setVisibility(View.VISIBLE);
+                    summary.setTextAlignment(TextView.TEXT_ALIGNMENT_TEXT_END);
+                    hasLine2 = true;
+                }
             } else {
                 icon1.setVisibility(View.GONE);
                 if (docSummary != null) {
@@ -762,32 +815,39 @@
     }
 
     private static class ThumbnailAsyncTask extends AsyncTask<Uri, Void, Bitmap> {
-        private final ImageView mTarget;
+        private final Uri mUri;
+        private final ImageView mIconMime;
+        private final ImageView mIconThumb;
         private final Point mThumbSize;
+        private final CancellationSignal mSignal;
 
-        public ThumbnailAsyncTask(ImageView target, Point thumbSize) {
-            mTarget = target;
+        public ThumbnailAsyncTask(
+                Uri uri, ImageView iconMime, ImageView iconThumb, Point thumbSize) {
+            mUri = uri;
+            mIconMime = iconMime;
+            mIconThumb = iconThumb;
             mThumbSize = thumbSize;
+            mSignal = new CancellationSignal();
         }
 
-        @Override
-        protected void onPreExecute() {
-            mTarget.setTag(this);
+        public void reallyCancel() {
+            cancel(false);
+            mSignal.cancel();
         }
 
         @Override
         protected Bitmap doInBackground(Uri... params) {
-            final Context context = mTarget.getContext();
-            final Uri uri = params[0];
+            final Context context = mIconThumb.getContext();
 
             Bitmap result = null;
             try {
+                // TODO: switch to using unstable provider
                 result = DocumentsContract.getDocumentThumbnail(
-                        context.getContentResolver(), uri, mThumbSize, null);
+                        context.getContentResolver(), mUri, mThumbSize, mSignal);
                 if (result != null) {
                     final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
                             context, mThumbSize);
-                    thumbs.put(uri, result);
+                    thumbs.put(mUri, result);
                 }
             } catch (Exception e) {
                 Log.w(TAG, "Failed to load thumbnail: " + e);
@@ -797,9 +857,14 @@
 
         @Override
         protected void onPostExecute(Bitmap result) {
-            if (mTarget.getTag() == this) {
-                mTarget.setImageBitmap(result);
-                mTarget.setTag(null);
+            if (mIconThumb.getTag() == this && result != null) {
+                mIconThumb.setTag(null);
+                mIconThumb.setImageBitmap(result);
+
+                mIconMime.setAlpha(1f);
+                mIconMime.animate().alpha(0f).start();
+                mIconThumb.setAlpha(0f);
+                mIconThumb.animate().alpha(1f).start();
             }
         }
     }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
index 1471836..334e262 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
@@ -62,6 +62,7 @@
 public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
     private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver();
 
+    private final int mType;
     private final RootInfo mRoot;
     private final DocumentInfo mDoc;
     private final Uri mUri;
@@ -70,9 +71,10 @@
     private CancellationSignal mSignal;
     private DirectoryResult mResult;
 
-    public DirectoryLoader(
-            Context context, RootInfo root, DocumentInfo doc, Uri uri, int userSortOrder) {
+    public DirectoryLoader(Context context, int type, RootInfo root, DocumentInfo doc, Uri uri,
+            int userSortOrder) {
         super(context);
+        mType = type;
         mRoot = root;
         mDoc = doc;
         mUri = uri;
@@ -128,6 +130,11 @@
             }
         }
 
+        // Search always uses ranking from provider
+        if (mType == DirectoryFragment.TYPE_SEARCH) {
+            result.sortOrder = State.SORT_ORDER_UNKNOWN;
+        }
+
         Log.d(TAG, "userMode=" + userMode + ", userSortOrder=" + mUserSortOrder + " --> mode="
                 + result.mode + ", sortOrder=" + result.sortOrder);
 
@@ -137,11 +144,18 @@
                     mUri, null, null, null, getQuerySortOrder(result.sortOrder), mSignal);
             cursor.registerContentObserver(mObserver);
 
-            final Cursor withRoot = new RootCursorWrapper(
-                    mUri.getAuthority(), mRoot.rootId, cursor, -1);
-            final Cursor sorted = new SortingCursorWrapper(withRoot, result.sortOrder);
+            cursor = new RootCursorWrapper(mUri.getAuthority(), mRoot.rootId, cursor, -1);
 
-            result.cursor = sorted;
+            if (mType == DirectoryFragment.TYPE_SEARCH) {
+                // Filter directories out of search results, for now
+                cursor = new FilteringCursorWrapper(cursor, null, new String[] {
+                        Document.MIME_TYPE_DIR });
+            } else {
+                // Normal directories should have sorting applied
+                cursor = new SortingCursorWrapper(cursor, result.sortOrder);
+            }
+
+            result.cursor = cursor;
         } catch (Exception e) {
             Log.d(TAG, "Failed to query", e);
             result.exception = e;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 79d2443..e89d388 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -50,6 +50,7 @@
 import android.view.LayoutInflater;
 import android.view.Menu;
 import android.view.MenuItem;
+import android.view.MenuItem.OnActionExpandListener;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
@@ -87,6 +88,7 @@
     private static final String EXTRA_STATE = "state";
 
     private boolean mIgnoreNextNavigation;
+    private boolean mIgnoreNextCollapse;
 
     private RootsCache mRoots;
     private State mState;
@@ -234,12 +236,14 @@
         public void onDrawerOpened(View drawerView) {
             mDrawerToggle.onDrawerOpened(drawerView);
             updateActionBar();
+            invalidateOptionsMenu();
         }
 
         @Override
         public void onDrawerClosed(View drawerView) {
             mDrawerToggle.onDrawerClosed(drawerView);
             updateActionBar();
+            invalidateOptionsMenu();
         }
 
         @Override
@@ -305,7 +309,6 @@
             public boolean onQueryTextSubmit(String query) {
                 mState.currentSearch = query;
                 onCurrentDirectoryChanged();
-                mSearchView.setIconified(true);
                 return true;
             }
 
@@ -315,12 +318,22 @@
             }
         });
 
-        mSearchView.setOnCloseListener(new OnCloseListener() {
+        searchMenu.setOnActionExpandListener(new OnActionExpandListener() {
             @Override
-            public boolean onClose() {
+            public boolean onMenuItemActionExpand(MenuItem item) {
+                return true;
+            }
+
+            @Override
+            public boolean onMenuItemActionCollapse(MenuItem item) {
+                if (mIgnoreNextCollapse) {
+                    mIgnoreNextCollapse = false;
+                    return true;
+                }
+
                 mState.currentSearch = null;
                 onCurrentDirectoryChanged();
-                return false;
+                return true;
             }
         });
 
@@ -342,6 +355,18 @@
         final MenuItem list = menu.findItem(R.id.menu_list);
         final MenuItem settings = menu.findItem(R.id.menu_settings);
 
+        // Open drawer means we hide most actions
+        if (mDrawerLayout.isDrawerOpen(mRootsContainer)) {
+            createDir.setVisible(false);
+            search.setVisible(false);
+            sort.setVisible(false);
+            grid.setVisible(false);
+            list.setVisible(false);
+            mIgnoreNextCollapse = true;
+            search.collapseActionView();
+            return true;
+        }
+
         if (cwd != null) {
             sort.setVisible(true);
             grid.setVisible(mState.derivedMode != MODE_GRID);
@@ -352,6 +377,17 @@
             list.setVisible(false);
         }
 
+        if (mState.currentSearch != null) {
+            // Search uses backend ranking; no sorting
+            sort.setVisible(false);
+
+            search.expandActionView();
+            mSearchView.setQuery(mState.currentSearch, false);
+        } else {
+            mIgnoreNextCollapse = true;
+            search.collapseActionView();
+        }
+
         // Only sort by size when visible
         sortSize.setVisible(mState.showSize);
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java
index 0a6cbc0..180ddef 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java
@@ -56,7 +56,11 @@
         packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
         packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         packageFilter.addDataScheme("package");
-        registerReceiver(mPackageReceiver, packageFilter);
+        registerReceiver(mCacheReceiver, packageFilter);
+
+        final IntentFilter localeFilter = new IntentFilter();
+        localeFilter.addAction(Intent.ACTION_LOCALE_CHANGED);
+        registerReceiver(mCacheReceiver, localeFilter);
     }
 
     @Override
@@ -70,7 +74,7 @@
         }
     }
 
-    private BroadcastReceiver mPackageReceiver = new BroadcastReceiver() {
+    private BroadcastReceiver mCacheReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             // TODO: narrow changed/removed to only packages that have backends
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
index 60f0103..5f56963 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
@@ -34,6 +34,10 @@
     private int mCount;
 
     public FilteringCursorWrapper(Cursor cursor, String[] acceptMimes) {
+        this(cursor, acceptMimes, null);
+    }
+
+    public FilteringCursorWrapper(Cursor cursor, String[] acceptMimes, String[] rejectMimes) {
         mCursor = cursor;
 
         final int count = cursor.getCount();
@@ -43,6 +47,9 @@
         while (cursor.moveToNext()) {
             final String mimeType = cursor.getString(
                     cursor.getColumnIndex(Document.COLUMN_MIME_TYPE));
+            if (rejectMimes != null && MimePredicate.mimeMatches(rejectMimes, mimeType)) {
+                continue;
+            }
             if (MimePredicate.mimeMatches(acceptMimes, mimeType)) {
                 mPosition[mCount++] = cursor.getPosition();
             }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
index 3642478..140373b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
@@ -183,12 +183,12 @@
                 convertView = inflater.inflate(R.layout.item_doc_list, parent, false);
             }
 
-            final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
+            final ImageView iconMime = (ImageView) convertView.findViewById(R.id.icon_mime);
             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));
+            iconMime.setImageDrawable(stack.root.loadIcon(context));
 
             final Drawable crumb = context.getResources()
                     .getDrawable(R.drawable.ic_breadcrumb_arrow);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
index 1fe5d54..af79c93 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
@@ -151,18 +151,18 @@
             case URI_RECENT:
                 final long cutoff = System.currentTimeMillis() - MAX_HISTORY_IN_MILLIS;
                 return db.query(TABLE_RECENT, projection, RecentColumns.TIMESTAMP + ">" + cutoff,
-                        null, null, null, null);
+                        null, null, null, sortOrder);
             case URI_STATE:
                 final String authority = uri.getPathSegments().get(1);
                 final String rootId = uri.getPathSegments().get(2);
                 final String documentId = uri.getPathSegments().get(3);
                 return db.query(TABLE_STATE, projection, StateColumns.AUTHORITY + "=? AND "
                         + StateColumns.ROOT_ID + "=? AND " + StateColumns.DOCUMENT_ID + "=?",
-                        new String[] { authority, rootId, documentId }, null, null, null);
+                        new String[] { authority, rootId, documentId }, null, null, sortOrder);
             case URI_RESUME:
                 final String packageName = uri.getPathSegments().get(1);
                 return db.query(TABLE_RESUME, projection, ResumeColumns.PACKAGE_NAME + "=?",
-                        new String[] { packageName }, null, null, null);
+                        new String[] { packageName }, null, null, sortOrder);
             default:
                 throw new UnsupportedOperationException("Unsupported Uri " + uri);
         }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index adf4701..b48674cf 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -109,6 +109,8 @@
                 }
             }
         }
+
+        Log.d(TAG, "Update found " + mRoots.size() + " roots");
     }
 
     @Deprecated
@@ -134,6 +136,21 @@
     }
 
     @GuardedBy("ActivityThread")
+    public boolean isIconUnique(RootInfo root) {
+        for (RootInfo test : mRoots) {
+            if (Objects.equal(test.authority, root.authority)) {
+                if (Objects.equal(test.rootId, root.rootId)) {
+                    continue;
+                }
+                if (test.icon == root.icon) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    @GuardedBy("ActivityThread")
     public RootInfo getRecentsRoot() {
         return mRecentsRoot;
     }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index efb972d..f3a21c6 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -25,6 +25,7 @@
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
 import android.provider.DocumentsContract.Root;
+import android.text.TextUtils;
 import android.text.format.Formatter;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -168,7 +169,7 @@
             }
 
             summary.setText(summaryText);
-            summary.setVisibility(summaryText != null ? View.VISIBLE : View.GONE);
+            summary.setVisibility(TextUtils.isEmpty(summaryText) ? View.GONE : View.VISIBLE);
 
             return convertView;
         }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index e0e8acf..b5a198c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -26,6 +26,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.provider.DocumentsContract.Root;
+import android.text.TextUtils;
 
 import com.android.documentsui.IconUtils;
 import com.android.documentsui.R;
@@ -203,6 +204,6 @@
     }
 
     public String getDirectoryString() {
-        return (summary != null) ? summary : title;
+        return !TextUtils.isEmpty(summary) ? summary : title;
     }
 }
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 2326ec2..ada3ad7 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -267,16 +267,15 @@
 
         final LinkedList<File> pending = new LinkedList<File>();
         pending.add(parent);
-        while (!pending.isEmpty() && result.getCount() < 20) {
+        while (!pending.isEmpty() && result.getCount() < 24) {
             final File file = pending.removeFirst();
             if (file.isDirectory()) {
                 for (File child : file.listFiles()) {
                     pending.add(child);
                 }
-            } else {
-                if (file.getName().toLowerCase().contains(query)) {
-                    includeFile(result, null, file);
-                }
+            }
+            if (file.getName().toLowerCase().contains(query)) {
+                includeFile(result, null, file);
             }
         }
         return result;