diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index fd96391b0c5f0..216509d47c204 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -18,6 +18,7 @@ package com.android.documentsui; 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 android.util.Log; 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 @@ public class RootsCache { // 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 @@ public class RootsCache { * 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 @@ public class RootsCache { 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/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index f908eebb32a3c..9f83c042b1119 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -45,7 +45,6 @@ import android.widget.ImageView; 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 class RootsFragment extends Fragment { public static class RootComparator implements Comparator { @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/packages/DocumentsUI/src/com/android/documentsui/Shared.java b/packages/DocumentsUI/src/com/android/documentsui/Shared.java index 22cb25a26a5c4..b90a1194596a6 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/Shared.java +++ b/packages/DocumentsUI/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 final class Shared { 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 @@ public final class Shared { ? (ArrayList) list : new ArrayList(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/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java index 0a9789f54e318..4583decc14a80 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -1071,8 +1071,8 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi 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/packages/DocumentsUI/src/com/android/documentsui/dirlist/Model.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/Model.java index b3694489aca2f..9684a5a0a2c28 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/Model.java +++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/Model.java @@ -35,6 +35,7 @@ import android.util.Log; 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 @@ public class Model { 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 @@ public class Model { 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/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java index 1c696ad88ef58..e9fdab090c18a 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java @@ -26,7 +26,6 @@ import android.provider.DocumentsContract; 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.DataOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.net.ProtocolException; -import java.text.Collator; import java.util.Objects; /** @@ -48,13 +46,6 @@ public class DocumentInfo implements Durable, Parcelable { 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 @@ public class DocumentInfo implements Durable, Parcelable { 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/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java index 3f4a1df2c5b6a..38970585afc39 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java +++ b/packages/DocumentsUI/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 com.android.documentsui.R; 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 { 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 @@ public class RootInfo implements Durable, Parcelable { /** Derived fields that aren't persisted */ public String[] derivedMimeTypes; public int derivedIcon; - public int derivedType; + public @RootType int derivedType; public RootInfo() { reset(); @@ -328,6 +344,22 @@ public class RootInfo implements Durable, Parcelable { return Objects.hash(authority, rootId); } + @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/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java index 83299f0677cfe..4b0bc41f19fee 100644 --- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java +++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelTest.java @@ -28,6 +28,7 @@ import android.test.suitebuilder.annotation.SmallTest; 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 @@ public class ModelTest extends AndroidTestCase { 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); } }