Merge "Undo generalization isDownloads > supportsChildren." into nyc-dev
diff --git a/src/com/android/documentsui/RootsCache.java b/src/com/android/documentsui/RootsCache.java
index fd96391..216509d 100644
--- a/src/com/android/documentsui/RootsCache.java
+++ b/src/com/android/documentsui/RootsCache.java
@@ -18,6 +18,7 @@
 
 import static com.android.documentsui.Shared.DEBUG;
 import static com.android.documentsui.Shared.TAG;
+import static com.android.internal.util.Preconditions.checkState;
 
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
@@ -40,6 +41,7 @@
 
 import com.android.documentsui.model.RootInfo;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
 
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Multimap;
@@ -85,15 +87,13 @@
 
         // Create a new anonymous "Recents" RootInfo. It's a faker.
         mRecentsRoot = new RootInfo() {{
-            // Special root for recents
-            authority = null;
-            rootId = null;
-            derivedIcon = R.drawable.ic_root_recent;
-            derivedType = RootInfo.TYPE_RECENTS;
-            flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_IS_CHILD;
-            title = mContext.getString(R.string.root_recent);
-            availableBytes = -1;
-        }};
+                // Special root for recents
+                derivedIcon = R.drawable.ic_root_recent;
+                derivedType = RootInfo.TYPE_RECENTS;
+                flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_IS_CHILD;
+                title = mContext.getString(R.string.root_recent);
+                availableBytes = -1;
+            }};
     }
 
     private class RootsChangedObserver extends ContentObserver {
@@ -116,6 +116,16 @@
      * Gather roots from all known storage providers.
      */
     public void updateAsync() {
+        // Verifying an assumption about the recents root being immutable.
+        if (DEBUG) {
+            checkState(mRecentsRoot.authority == null);
+            checkState(mRecentsRoot.rootId == null);
+            checkState(mRecentsRoot.derivedIcon == R.drawable.ic_root_recent);
+            checkState(mRecentsRoot.derivedType == RootInfo.TYPE_RECENTS);
+            checkState(mRecentsRoot.flags == (Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_IS_CHILD));
+            checkState(mRecentsRoot.title == mContext.getString(R.string.root_recent));
+            checkState(mRecentsRoot.availableBytes == -1);
+        }
         new UpdateTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
     }
 
@@ -412,9 +422,10 @@
             if (!state.showAdvanced && root.isAdvanced()) continue;
             // Exclude non-local devices when local only
             if (state.localOnly && !root.isLocalOnly()) continue;
-            // Exclude downloads roots that don't support directory creation
-            // TODO: Add flag to check the root supports directory creation or not.
-            if (state.directoryCopy && !root.supportsChildren()) continue;
+            // Exclude downloads roots as it doesn't support directory creation (actually
+            // we just don't show them).
+            // TODO: Add flag to check the root supports directory creation.
+            if (state.directoryCopy && !root.isDownloads()) continue;
 
             // Only show empty roots when creating, or in browse mode.
             if (root.isEmpty() && (state.action == State.ACTION_OPEN
diff --git a/src/com/android/documentsui/RootsFragment.java b/src/com/android/documentsui/RootsFragment.java
index f908eeb..9f83c04 100644
--- a/src/com/android/documentsui/RootsFragment.java
+++ b/src/com/android/documentsui/RootsFragment.java
@@ -45,7 +45,6 @@
 import android.widget.ListView;
 import android.widget.TextView;
 
-import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.RootInfo;
 
 import java.util.ArrayList;
@@ -403,17 +402,7 @@
     public static class RootComparator implements Comparator<RootItem> {
         @Override
         public int compare(RootItem lhs, RootItem rhs) {
-            // Sort by root type, then title, then summary.
-            int score = lhs.root.derivedType - rhs.root.derivedType;
-            if (score != 0) {
-                return score;
-            }
-            score = DocumentInfo.compareToIgnoreCaseNullable(lhs.root.title, rhs.root.title);
-            if (score != 0) {
-                return score;
-            }
-
-            return DocumentInfo.compareToIgnoreCaseNullable(lhs.root.summary, rhs.root.summary);
+            return lhs.root.compareTo(rhs.root);
         }
     }
 }
diff --git a/src/com/android/documentsui/Shared.java b/src/com/android/documentsui/Shared.java
index 22cb25a..b90a119 100644
--- a/src/com/android/documentsui/Shared.java
+++ b/src/com/android/documentsui/Shared.java
@@ -17,14 +17,17 @@
 package com.android.documentsui;
 
 import android.content.Context;
+import android.text.TextUtils;
 import android.text.format.DateUtils;
 import android.text.format.Time;
 
+import java.text.Collator;
 import java.util.ArrayList;
 import java.util.List;
 
 /** @hide */
 public final class Shared {
+
     /** Intent action name to pick a copy destination. */
     public static final String ACTION_PICK_COPY_DESTINATION =
             "com.android.documentsui.PICK_COPY_DESTINATION";
@@ -39,6 +42,19 @@
     public static final String TAG = "Documents";
     public static final String EXTRA_STACK = "com.android.documentsui.STACK";
 
+
+    /**
+     * String prefix used to indicate the document is a directory.
+     */
+    public static final char DIR_PREFIX = '\001';
+
+    private static final Collator sCollator;
+
+    static {
+        sCollator = Collator.getInstance();
+        sCollator.setStrength(Collator.SECONDARY);
+    }
+
     /**
      * Generates a formatted quantity string.
      */
@@ -76,4 +92,26 @@
             ? (ArrayList<T>) list
             : new ArrayList<T>(list);
     }
+
+    /**
+     * Compare two strings against each other using system default collator in a
+     * case-insensitive mode. Clusters strings prefixed with {@link DIR_PREFIX}
+     * before other items.
+     */
+    public static int compareToIgnoreCaseNullable(String lhs, String rhs) {
+        final boolean leftEmpty = TextUtils.isEmpty(lhs);
+        final boolean rightEmpty = TextUtils.isEmpty(rhs);
+
+        if (leftEmpty && rightEmpty) return 0;
+        if (leftEmpty) return -1;
+        if (rightEmpty) return 1;
+
+        final boolean leftDir = (lhs.charAt(0) == DIR_PREFIX);
+        final boolean rightDir = (rhs.charAt(0) == DIR_PREFIX);
+
+        if (leftDir && !rightDir) return -1;
+        if (rightDir && !leftDir) return 1;
+
+        return sCollator.compare(lhs, rhs);
+    }
 }
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 0a9789f..4583dec 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -1071,8 +1071,8 @@
             return false;
         }
 
-        // Can't copy folders to roots that don't support children.
-        if (!root.supportsChildren()) {
+        // Can't copy folders to downloads, because we don't show folders there.
+        if (!root.isDownloads()) {
             for (DocumentInfo docs : files) {
                 if (docs.isDirectory()) {
                     return false;
diff --git a/src/com/android/documentsui/dirlist/Model.java b/src/com/android/documentsui/dirlist/Model.java
index b369448..9684a5a 100644
--- a/src/com/android/documentsui/dirlist/Model.java
+++ b/src/com/android/documentsui/dirlist/Model.java
@@ -35,6 +35,7 @@
 
 import com.android.documentsui.DirectoryResult;
 import com.android.documentsui.RootCursorWrapper;
+import com.android.documentsui.Shared;
 import com.android.documentsui.dirlist.MultiSelectManager.Selection;
 import com.android.documentsui.model.DocumentInfo;
 
@@ -170,7 +171,7 @@
                     final String displayName = getCursorString(
                             mCursor, Document.COLUMN_DISPLAY_NAME);
                     if (Document.MIME_TYPE_DIR.equals(mimeType)) {
-                        stringValues[pos] = DocumentInfo.DIR_PREFIX + displayName;
+                        stringValues[pos] = Shared.DIR_PREFIX + displayName;
                     } else {
                         stringValues[pos] = displayName;
                     }
@@ -227,7 +228,7 @@
 
                 final String lhs = pivotValue;
                 final String rhs = sortKey[mid];
-                final int compare = DocumentInfo.compareToIgnoreCaseNullable(lhs, rhs);
+                final int compare = Shared.compareToIgnoreCaseNullable(lhs, rhs);
 
                 if (compare < 0) {
                     right = mid;
diff --git a/src/com/android/documentsui/model/DocumentInfo.java b/src/com/android/documentsui/model/DocumentInfo.java
index 1c696ad..e9fdab0 100644
--- a/src/com/android/documentsui/model/DocumentInfo.java
+++ b/src/com/android/documentsui/model/DocumentInfo.java
@@ -26,7 +26,6 @@
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsProvider;
 import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
 
 import com.android.documentsui.DocumentsApplication;
 import com.android.documentsui.RootCursorWrapper;
@@ -38,7 +37,6 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.net.ProtocolException;
-import java.text.Collator;
 import java.util.Objects;
 
 /**
@@ -48,13 +46,6 @@
     private static final int VERSION_INIT = 1;
     private static final int VERSION_SPLIT_URI = 2;
 
-    private static final Collator sCollator;
-
-    static {
-        sCollator = Collator.getInstance();
-        sCollator.setStrength(Collator.SECONDARY);
-    }
-
     public String authority;
     public String documentId;
     public String mimeType;
@@ -320,31 +311,4 @@
         fnfe.initCause(t);
         throw fnfe;
     }
-
-    /**
-     * String prefix used to indicate the document is a directory.
-     */
-    public static final char DIR_PREFIX = '\001';
-
-    /**
-     * Compare two strings against each other using system default collator in a
-     * case-insensitive mode. Clusters strings prefixed with {@link #DIR_PREFIX}
-     * before other items.
-     */
-    public static int compareToIgnoreCaseNullable(String lhs, String rhs) {
-        final boolean leftEmpty = TextUtils.isEmpty(lhs);
-        final boolean rightEmpty = TextUtils.isEmpty(rhs);
-
-        if (leftEmpty && rightEmpty) return 0;
-        if (leftEmpty) return -1;
-        if (rightEmpty) return 1;
-
-        final boolean leftDir = (lhs.charAt(0) == DIR_PREFIX);
-        final boolean rightDir = (rhs.charAt(0) == DIR_PREFIX);
-
-        if (leftDir && !rightDir) return -1;
-        if (rightDir && !leftDir) return 1;
-
-        return sCollator.compare(lhs, rhs);
-    }
 }
diff --git a/src/com/android/documentsui/model/RootInfo.java b/src/com/android/documentsui/model/RootInfo.java
index 3f4a1df..3897058 100644
--- a/src/com/android/documentsui/model/RootInfo.java
+++ b/src/com/android/documentsui/model/RootInfo.java
@@ -16,10 +16,12 @@
 
 package com.android.documentsui.model;
 
+import static com.android.documentsui.Shared.compareToIgnoreCaseNullable;
 import static com.android.documentsui.model.DocumentInfo.getCursorInt;
 import static com.android.documentsui.model.DocumentInfo.getCursorLong;
 import static com.android.documentsui.model.DocumentInfo.getCursorString;
 
+import android.annotation.IntDef;
 import android.content.Context;
 import android.database.Cursor;
 import android.graphics.drawable.Drawable;
@@ -36,17 +38,31 @@
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.net.ProtocolException;
 import java.util.Objects;
 
 /**
  * Representation of a {@link Root}.
  */
-public class RootInfo implements Durable, Parcelable {
+public class RootInfo implements Durable, Parcelable, Comparable<RootInfo> {
     private static final int VERSION_INIT = 1;
     private static final int VERSION_DROP_TYPE = 2;
 
     // The values of these constants determine the sort order of various roots in the RootsFragment.
+    @IntDef(flag = true, value = {
+            TYPE_IMAGES,
+            TYPE_VIDEO,
+            TYPE_AUDIO,
+            TYPE_RECENTS,
+            TYPE_DOWNLOADS,
+            TYPE_LOCAL,
+            TYPE_MTP,
+            TYPE_OTHER
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RootType {}
     public static final int TYPE_IMAGES = 1;
     public static final int TYPE_VIDEO = 2;
     public static final int TYPE_AUDIO = 3;
@@ -69,7 +85,7 @@
     /** Derived fields that aren't persisted */
     public String[] derivedMimeTypes;
     public int derivedIcon;
-    public int derivedType;
+    public @RootType int derivedType;
 
     public RootInfo() {
         reset();
@@ -329,6 +345,22 @@
     }
 
     @Override
+    public int compareTo(RootInfo other) {
+        // Sort by root type, then title, then summary.
+        int score = derivedType - other.derivedType;
+        if (score != 0) {
+            return score;
+        }
+
+        score = compareToIgnoreCaseNullable(title, other.title);
+        if (score != 0) {
+            return score;
+        }
+
+        return compareToIgnoreCaseNullable(summary, other.summary);
+    }
+
+    @Override
     public String toString() {
         return "Root{authority=" + authority + ", rootId=" + rootId + ", title=" + title + "}";
     }
diff --git a/tests/src/com/android/documentsui/dirlist/ModelTest.java b/tests/src/com/android/documentsui/dirlist/ModelTest.java
index 83299f0..4b0bc41 100644
--- a/tests/src/com/android/documentsui/dirlist/ModelTest.java
+++ b/tests/src/com/android/documentsui/dirlist/ModelTest.java
@@ -28,6 +28,7 @@
 
 import com.android.documentsui.DirectoryResult;
 import com.android.documentsui.RootCursorWrapper;
+import com.android.documentsui.Shared;
 import com.android.documentsui.State;
 import com.android.documentsui.dirlist.MultiSelectManager.Selection;
 import com.android.documentsui.model.DocumentInfo;
@@ -190,7 +191,7 @@
 
         assertEquals(ITEM_COUNT, seen.cardinality());
         for (int i = 0; i < names.size()-1; ++i) {
-            assertTrue(DocumentInfo.compareToIgnoreCaseNullable(names.get(i), names.get(i+1)) <= 0);
+            assertTrue(Shared.compareToIgnoreCaseNullable(names.get(i), names.get(i+1)) <= 0);
         }
     }