Merge "Undo generalization isDownloads > supportsChildren." into nyc-dev

This commit is contained in:
Steve McKay
2016-02-19 19:17:58 +00:00
committed by Android (Google) Code Review
8 changed files with 103 additions and 67 deletions

View File

@@ -18,6 +18,7 @@ package com.android.documentsui;
import static com.android.documentsui.Shared.DEBUG; import static com.android.documentsui.Shared.DEBUG;
import static com.android.documentsui.Shared.TAG; import static com.android.documentsui.Shared.TAG;
import static com.android.internal.util.Preconditions.checkState;
import android.content.ContentProviderClient; import android.content.ContentProviderClient;
import android.content.ContentResolver; import android.content.ContentResolver;
@@ -40,6 +41,7 @@ import android.util.Log;
import com.android.documentsui.model.RootInfo; import com.android.documentsui.model.RootInfo;
import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap; import com.google.common.collect.Multimap;
@@ -85,15 +87,13 @@ public class RootsCache {
// Create a new anonymous "Recents" RootInfo. It's a faker. // Create a new anonymous "Recents" RootInfo. It's a faker.
mRecentsRoot = new RootInfo() {{ mRecentsRoot = new RootInfo() {{
// Special root for recents // Special root for recents
authority = null; derivedIcon = R.drawable.ic_root_recent;
rootId = null; derivedType = RootInfo.TYPE_RECENTS;
derivedIcon = R.drawable.ic_root_recent; flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_IS_CHILD;
derivedType = RootInfo.TYPE_RECENTS; title = mContext.getString(R.string.root_recent);
flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_IS_CHILD; availableBytes = -1;
title = mContext.getString(R.string.root_recent); }};
availableBytes = -1;
}};
} }
private class RootsChangedObserver extends ContentObserver { private class RootsChangedObserver extends ContentObserver {
@@ -116,6 +116,16 @@ public class RootsCache {
* Gather roots from all known storage providers. * Gather roots from all known storage providers.
*/ */
public void updateAsync() { 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); new UpdateTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
@@ -412,9 +422,10 @@ public class RootsCache {
if (!state.showAdvanced && root.isAdvanced()) continue; if (!state.showAdvanced && root.isAdvanced()) continue;
// Exclude non-local devices when local only // Exclude non-local devices when local only
if (state.localOnly && !root.isLocalOnly()) continue; if (state.localOnly && !root.isLocalOnly()) continue;
// Exclude downloads roots that don't support directory creation // Exclude downloads roots as it doesn't support directory creation (actually
// TODO: Add flag to check the root supports directory creation or not. // we just don't show them).
if (state.directoryCopy && !root.supportsChildren()) continue; // 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. // Only show empty roots when creating, or in browse mode.
if (root.isEmpty() && (state.action == State.ACTION_OPEN if (root.isEmpty() && (state.action == State.ACTION_OPEN

View File

@@ -45,7 +45,6 @@ import android.widget.ImageView;
import android.widget.ListView; import android.widget.ListView;
import android.widget.TextView; import android.widget.TextView;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.RootInfo; import com.android.documentsui.model.RootInfo;
import java.util.ArrayList; import java.util.ArrayList;
@@ -403,17 +402,7 @@ public class RootsFragment extends Fragment {
public static class RootComparator implements Comparator<RootItem> { public static class RootComparator implements Comparator<RootItem> {
@Override @Override
public int compare(RootItem lhs, RootItem rhs) { public int compare(RootItem lhs, RootItem rhs) {
// Sort by root type, then title, then summary. return lhs.root.compareTo(rhs.root);
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);
} }
} }
} }

View File

@@ -17,14 +17,17 @@
package com.android.documentsui; package com.android.documentsui;
import android.content.Context; import android.content.Context;
import android.text.TextUtils;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.text.format.Time; import android.text.format.Time;
import java.text.Collator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/** @hide */ /** @hide */
public final class Shared { public final class Shared {
/** Intent action name to pick a copy destination. */ /** Intent action name to pick a copy destination. */
public static final String ACTION_PICK_COPY_DESTINATION = public static final String ACTION_PICK_COPY_DESTINATION =
"com.android.documentsui.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 TAG = "Documents";
public static final String EXTRA_STACK = "com.android.documentsui.STACK"; 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. * Generates a formatted quantity string.
*/ */
@@ -76,4 +92,26 @@ public final class Shared {
? (ArrayList<T>) list ? (ArrayList<T>) list
: new 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);
}
} }

View File

@@ -1071,8 +1071,8 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
return false; return false;
} }
// Can't copy folders to roots that don't support children. // Can't copy folders to downloads, because we don't show folders there.
if (!root.supportsChildren()) { if (!root.isDownloads()) {
for (DocumentInfo docs : files) { for (DocumentInfo docs : files) {
if (docs.isDirectory()) { if (docs.isDirectory()) {
return false; return false;

View File

@@ -35,6 +35,7 @@ import android.util.Log;
import com.android.documentsui.DirectoryResult; import com.android.documentsui.DirectoryResult;
import com.android.documentsui.RootCursorWrapper; import com.android.documentsui.RootCursorWrapper;
import com.android.documentsui.Shared;
import com.android.documentsui.dirlist.MultiSelectManager.Selection; import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.DocumentInfo;
@@ -170,7 +171,7 @@ public class Model {
final String displayName = getCursorString( final String displayName = getCursorString(
mCursor, Document.COLUMN_DISPLAY_NAME); mCursor, Document.COLUMN_DISPLAY_NAME);
if (Document.MIME_TYPE_DIR.equals(mimeType)) { if (Document.MIME_TYPE_DIR.equals(mimeType)) {
stringValues[pos] = DocumentInfo.DIR_PREFIX + displayName; stringValues[pos] = Shared.DIR_PREFIX + displayName;
} else { } else {
stringValues[pos] = displayName; stringValues[pos] = displayName;
} }
@@ -227,7 +228,7 @@ public class Model {
final String lhs = pivotValue; final String lhs = pivotValue;
final String rhs = sortKey[mid]; final String rhs = sortKey[mid];
final int compare = DocumentInfo.compareToIgnoreCaseNullable(lhs, rhs); final int compare = Shared.compareToIgnoreCaseNullable(lhs, rhs);
if (compare < 0) { if (compare < 0) {
right = mid; right = mid;

View File

@@ -26,7 +26,6 @@ import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Document;
import android.provider.DocumentsProvider; import android.provider.DocumentsProvider;
import android.support.annotation.VisibleForTesting; import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
import com.android.documentsui.DocumentsApplication; import com.android.documentsui.DocumentsApplication;
import com.android.documentsui.RootCursorWrapper; import com.android.documentsui.RootCursorWrapper;
@@ -38,7 +37,6 @@ import java.io.DataOutputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.net.ProtocolException; import java.net.ProtocolException;
import java.text.Collator;
import java.util.Objects; 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_INIT = 1;
private static final int VERSION_SPLIT_URI = 2; 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 authority;
public String documentId; public String documentId;
public String mimeType; public String mimeType;
@@ -320,31 +311,4 @@ public class DocumentInfo implements Durable, Parcelable {
fnfe.initCause(t); fnfe.initCause(t);
throw fnfe; 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);
}
} }

View File

@@ -16,10 +16,12 @@
package com.android.documentsui.model; 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.getCursorInt;
import static com.android.documentsui.model.DocumentInfo.getCursorLong; import static com.android.documentsui.model.DocumentInfo.getCursorLong;
import static com.android.documentsui.model.DocumentInfo.getCursorString; import static com.android.documentsui.model.DocumentInfo.getCursorString;
import android.annotation.IntDef;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
@@ -36,17 +38,31 @@ import com.android.documentsui.R;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.ProtocolException; import java.net.ProtocolException;
import java.util.Objects; import java.util.Objects;
/** /**
* Representation of a {@link Root}. * 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_INIT = 1;
private static final int VERSION_DROP_TYPE = 2; private static final int VERSION_DROP_TYPE = 2;
// The values of these constants determine the sort order of various roots in the RootsFragment. // 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_IMAGES = 1;
public static final int TYPE_VIDEO = 2; public static final int TYPE_VIDEO = 2;
public static final int TYPE_AUDIO = 3; public static final int TYPE_AUDIO = 3;
@@ -69,7 +85,7 @@ public class RootInfo implements Durable, Parcelable {
/** Derived fields that aren't persisted */ /** Derived fields that aren't persisted */
public String[] derivedMimeTypes; public String[] derivedMimeTypes;
public int derivedIcon; public int derivedIcon;
public int derivedType; public @RootType int derivedType;
public RootInfo() { public RootInfo() {
reset(); reset();
@@ -328,6 +344,22 @@ public class RootInfo implements Durable, Parcelable {
return Objects.hash(authority, rootId); 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 @Override
public String toString() { public String toString() {
return "Root{authority=" + authority + ", rootId=" + rootId + ", title=" + title + "}"; return "Root{authority=" + authority + ", rootId=" + rootId + ", title=" + title + "}";

View File

@@ -28,6 +28,7 @@ import android.test.suitebuilder.annotation.SmallTest;
import com.android.documentsui.DirectoryResult; import com.android.documentsui.DirectoryResult;
import com.android.documentsui.RootCursorWrapper; import com.android.documentsui.RootCursorWrapper;
import com.android.documentsui.Shared;
import com.android.documentsui.State; import com.android.documentsui.State;
import com.android.documentsui.dirlist.MultiSelectManager.Selection; import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.DocumentInfo;
@@ -190,7 +191,7 @@ public class ModelTest extends AndroidTestCase {
assertEquals(ITEM_COUNT, seen.cardinality()); assertEquals(ITEM_COUNT, seen.cardinality());
for (int i = 0; i < names.size()-1; ++i) { 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);
} }
} }