Merge "Make RecentsLoader load on a per-authority basis instead of per-root." into nyc-andromeda-dev
diff --git a/src/com/android/documentsui/RecentsLoader.java b/src/com/android/documentsui/RecentsLoader.java
index 9cb8f7e..02c01ee 100644
--- a/src/com/android/documentsui/RecentsLoader.java
+++ b/src/com/android/documentsui/RecentsLoader.java
@@ -40,16 +40,17 @@
 import com.android.documentsui.roots.RootsCache;
 import com.android.internal.annotations.GuardedBy;
 
-import libcore.io.IoUtils;
-
 import com.google.common.util.concurrent.AbstractFuture;
 
+import libcore.io.IoUtils;
+
 import java.io.Closeable;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Semaphore;
@@ -83,7 +84,8 @@
     private final State mState;
 
     @GuardedBy("mTasks")
-    private final HashMap<RootInfo, RecentsTask> mTasks = new HashMap<>();
+    /** A authority -> RecentsTask map */
+    private final Map<String, RecentsTask> mTasks = new HashMap<>();
 
     private CountDownLatch mFirstPassLatch;
     private volatile boolean mFirstPassDone;
@@ -114,12 +116,10 @@
         if (mFirstPassLatch == null) {
             // First time through we kick off all the recent tasks, and wait
             // around to see if everyone finishes quickly.
+            Map<String, List<String>> rootsIndex = indexRecentsRoots();
 
-            final Collection<RootInfo> roots = mRoots.getMatchingRootsBlocking(mState);
-            for (RootInfo root : roots) {
-                if (root.supportsRecents()) {
-                    mTasks.put(root, new RecentsTask(root.authority, root.rootId));
-                }
+            for (String authority : rootsIndex.keySet()) {
+                mTasks.put(authority, new RecentsTask(authority, rootsIndex.get(authority)));
             }
 
             mFirstPassLatch = new CountDownLatch(mTasks.size());
@@ -139,21 +139,31 @@
 
         // Collect all finished tasks
         boolean allDone = true;
+        int totalQuerySize = 0;
         List<Cursor> cursors = new ArrayList<>();
         for (RecentsTask task : mTasks.values()) {
             if (task.isDone()) {
                 try {
-                    final Cursor cursor = task.get();
-                    if (cursor == null) continue;
+                    final Cursor[] taskCursors = task.get();
+                    if (taskCursors == null || taskCursors.length == 0) continue;
 
-                    final FilteringCursorWrapper filtered = new FilteringCursorWrapper(
-                            cursor, mState.acceptMimes, RECENT_REJECT_MIMES, rejectBefore) {
-                        @Override
-                        public void close() {
-                            // Ignored, since we manage cursor lifecycle internally
+                    totalQuerySize += taskCursors.length;
+                    for (Cursor cursor : taskCursors) {
+                        if (cursor == null) {
+                            // It's possible given an authority, some roots fail to return a cursor
+                            // after a query.
+                            continue;
                         }
-                    };
-                    cursors.add(filtered);
+                        final FilteringCursorWrapper filtered = new FilteringCursorWrapper(
+                                cursor, mState.acceptMimes, RECENT_REJECT_MIMES, rejectBefore) {
+                            @Override
+                            public void close() {
+                                // Ignored, since we manage cursor lifecycle internally
+                            }
+                        };
+                        cursors.add(filtered);
+                    }
+
                 } catch (InterruptedException e) {
                     throw new RuntimeException(e);
                 } catch (ExecutionException e) {
@@ -165,7 +175,8 @@
         }
 
         if (DEBUG) {
-            Log.d(TAG, "Found " + cursors.size() + " of " + mTasks.size() + " recent queries done");
+            Log.d(TAG,
+                    "Found " + cursors.size() + " of " + totalQuerySize + " recent queries done");
         }
 
         final DirectoryResult result = new DirectoryResult();
@@ -190,6 +201,26 @@
         return result;
     }
 
+    /**
+     * Returns a map of Authority -> rootIds
+     */
+    private Map<String, List<String>> indexRecentsRoots() {
+        final Collection<RootInfo> roots = mRoots.getMatchingRootsBlocking(mState);
+        HashMap<String, List<String>> rootsIndex = new HashMap<>();
+        for (RootInfo root : roots) {
+            if (!root.supportsRecents()) {
+                continue;
+            }
+
+            if (!rootsIndex.containsKey(root.authority)) {
+                rootsIndex.put(root.authority, new ArrayList<>());
+            }
+            rootsIndex.get(root.authority).add(root.rootId);
+        }
+
+        return rootsIndex;
+    }
+
     @Override
     public void cancelLoadInBackground() {
         super.cancelLoadInBackground();
@@ -253,15 +284,15 @@
     // TODO: create better transfer of ownership around cursor to ensure its
     // closed in all edge cases.
 
-    public class RecentsTask extends AbstractFuture<Cursor> implements Runnable, Closeable {
+    public class RecentsTask extends AbstractFuture<Cursor[]> implements Runnable, Closeable {
         public final String authority;
-        public final String rootId;
+        public final List<String> rootIds;
 
-        private Cursor mWithRoot;
+        private Cursor[] mCursors;
 
-        public RecentsTask(String authority, String rootId) {
+        public RecentsTask(String authority, List<String> rootIds) {
             this.authority = authority;
-            this.rootId = rootId;
+            this.rootIds = rootIds;
         }
 
         @Override
@@ -287,18 +318,28 @@
                 client = DocumentsApplication.acquireUnstableProviderOrThrow(
                         getContext().getContentResolver(), authority);
 
-                final Uri uri = DocumentsContract.buildRecentDocumentsUri(authority, rootId);
-                final Cursor cursor = client.query(
-                        uri, null, null, null, mState.sortModel.getDocumentSortQuery());
-                mWithRoot = new RootCursorWrapper(authority, rootId, cursor, MAX_DOCS_FROM_ROOT);
+                final Cursor[] res = new Cursor[rootIds.size()];
+                mCursors = new Cursor[rootIds.size()];
+                for (int i = 0; i < rootIds.size(); i++) {
+                    final Uri uri = DocumentsContract.buildRecentDocumentsUri(authority,
+                            rootIds.get(i));
+                    try {
+                        res[i] = client.query(
+                                uri, null, null, null, mState.sortModel.getDocumentSortQuery());
+                        mCursors[i] = new RootCursorWrapper(authority, rootIds.get(i), res[i],
+                                MAX_DOCS_FROM_ROOT);
+                    } catch (Exception e) {
+                        Log.w(TAG, "Failed to load " + authority + ", " + rootIds.get(i), e);
+                    }
+                }
 
             } catch (Exception e) {
-                Log.w(TAG, "Failed to load " + authority + ", " + rootId, e);
+                Log.w(TAG, "Failed to acquire content resolver for authority: " + authority);
             } finally {
                 ContentProviderClient.releaseQuietly(client);
             }
 
-            set(mWithRoot);
+            set(mCursors);
 
             mFirstPassLatch.countDown();
             if (mFirstPassDone) {
@@ -308,7 +349,9 @@
 
         @Override
         public void close() throws IOException {
-            IoUtils.closeQuietly(mWithRoot);
+            for (Cursor cursor : mCursors) {
+                IoUtils.closeQuietly(cursor);
+            }
         }
     }
 }