Add structured sort data to ContentResolver.query.
Update DocumentsProvider to override
ContentProvider#query(Uri, String[], Bundle, CancellationSignal);
Added an otherwise unneeded import to pass doc check
on DocumentsProvider.
Bug: 30927484
Change-Id: I295c21f53901d567455286f22439f21d22a8a25a
Test: Build and run. Test from DocsUi.
This commit is contained in:
@@ -8197,9 +8197,14 @@ package android.content {
|
||||
field public static final java.lang.String EXTRA_SIZE = "android.content.extra.SIZE";
|
||||
field public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 2; // 0x2
|
||||
field public static final int NOTIFY_SYNC_TO_NETWORK = 1; // 0x1
|
||||
field public static final java.lang.String QUERY_ARG_SELECTION = "android:query-selection";
|
||||
field public static final java.lang.String QUERY_ARG_SELECTION_ARGS = "android:query-selection-args";
|
||||
field public static final java.lang.String QUERY_ARG_SORT_ORDER = "android:query-sort-order";
|
||||
field public static final java.lang.String QUERY_ARG_SORT_COLLATION = "android:query-sort-collation";
|
||||
field public static final java.lang.String QUERY_ARG_SORT_COLUMNS = "android:query-sort-columns";
|
||||
field public static final java.lang.String QUERY_ARG_SORT_DIRECTION = "android:query-sort-direction";
|
||||
field public static final java.lang.String QUERY_ARG_SQL_SELECTION = "android:query-sql-selection";
|
||||
field public static final java.lang.String QUERY_ARG_SQL_SELECTION_ARGS = "android:query-sql-selection-args";
|
||||
field public static final java.lang.String QUERY_ARG_SQL_SORT_ORDER = "android:query-sql-sort-order";
|
||||
field public static final int QUERY_SORT_DIRECTION_ASCENDING = 0; // 0x0
|
||||
field public static final int QUERY_SORT_DIRECTION_DESCENDING = 1; // 0x1
|
||||
field public static final java.lang.String SCHEME_ANDROID_RESOURCE = "android.resource";
|
||||
field public static final java.lang.String SCHEME_CONTENT = "content";
|
||||
field public static final java.lang.String SCHEME_FILE = "file";
|
||||
@@ -32556,7 +32561,10 @@ package android.provider {
|
||||
method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
|
||||
method public android.content.res.AssetFileDescriptor openTypedDocument(java.lang.String, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
|
||||
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
|
||||
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal);
|
||||
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal);
|
||||
method public abstract android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], java.lang.String) throws java.io.FileNotFoundException;
|
||||
method public android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], android.os.Bundle) throws java.io.FileNotFoundException;
|
||||
method public abstract android.database.Cursor queryDocument(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
|
||||
method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
|
||||
method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException;
|
||||
|
||||
@@ -8541,9 +8541,14 @@ package android.content {
|
||||
field public static final java.lang.String EXTRA_SIZE = "android.content.extra.SIZE";
|
||||
field public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 2; // 0x2
|
||||
field public static final int NOTIFY_SYNC_TO_NETWORK = 1; // 0x1
|
||||
field public static final java.lang.String QUERY_ARG_SELECTION = "android:query-selection";
|
||||
field public static final java.lang.String QUERY_ARG_SELECTION_ARGS = "android:query-selection-args";
|
||||
field public static final java.lang.String QUERY_ARG_SORT_ORDER = "android:query-sort-order";
|
||||
field public static final java.lang.String QUERY_ARG_SORT_COLLATION = "android:query-sort-collation";
|
||||
field public static final java.lang.String QUERY_ARG_SORT_COLUMNS = "android:query-sort-columns";
|
||||
field public static final java.lang.String QUERY_ARG_SORT_DIRECTION = "android:query-sort-direction";
|
||||
field public static final java.lang.String QUERY_ARG_SQL_SELECTION = "android:query-sql-selection";
|
||||
field public static final java.lang.String QUERY_ARG_SQL_SELECTION_ARGS = "android:query-sql-selection-args";
|
||||
field public static final java.lang.String QUERY_ARG_SQL_SORT_ORDER = "android:query-sql-sort-order";
|
||||
field public static final int QUERY_SORT_DIRECTION_ASCENDING = 0; // 0x0
|
||||
field public static final int QUERY_SORT_DIRECTION_DESCENDING = 1; // 0x1
|
||||
field public static final java.lang.String SCHEME_ANDROID_RESOURCE = "android.resource";
|
||||
field public static final java.lang.String SCHEME_CONTENT = "content";
|
||||
field public static final java.lang.String SCHEME_FILE = "file";
|
||||
@@ -35306,7 +35311,10 @@ package android.provider {
|
||||
method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
|
||||
method public android.content.res.AssetFileDescriptor openTypedDocument(java.lang.String, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
|
||||
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
|
||||
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal);
|
||||
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal);
|
||||
method public abstract android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], java.lang.String) throws java.io.FileNotFoundException;
|
||||
method public android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], android.os.Bundle) throws java.io.FileNotFoundException;
|
||||
method public abstract android.database.Cursor queryDocument(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
|
||||
method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
|
||||
method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException;
|
||||
|
||||
@@ -8220,9 +8220,14 @@ package android.content {
|
||||
field public static final java.lang.String EXTRA_SIZE = "android.content.extra.SIZE";
|
||||
field public static final int NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS = 2; // 0x2
|
||||
field public static final int NOTIFY_SYNC_TO_NETWORK = 1; // 0x1
|
||||
field public static final java.lang.String QUERY_ARG_SELECTION = "android:query-selection";
|
||||
field public static final java.lang.String QUERY_ARG_SELECTION_ARGS = "android:query-selection-args";
|
||||
field public static final java.lang.String QUERY_ARG_SORT_ORDER = "android:query-sort-order";
|
||||
field public static final java.lang.String QUERY_ARG_SORT_COLLATION = "android:query-sort-collation";
|
||||
field public static final java.lang.String QUERY_ARG_SORT_COLUMNS = "android:query-sort-columns";
|
||||
field public static final java.lang.String QUERY_ARG_SORT_DIRECTION = "android:query-sort-direction";
|
||||
field public static final java.lang.String QUERY_ARG_SQL_SELECTION = "android:query-sql-selection";
|
||||
field public static final java.lang.String QUERY_ARG_SQL_SELECTION_ARGS = "android:query-sql-selection-args";
|
||||
field public static final java.lang.String QUERY_ARG_SQL_SORT_ORDER = "android:query-sql-sort-order";
|
||||
field public static final int QUERY_SORT_DIRECTION_ASCENDING = 0; // 0x0
|
||||
field public static final int QUERY_SORT_DIRECTION_DESCENDING = 1; // 0x1
|
||||
field public static final java.lang.String SCHEME_ANDROID_RESOURCE = "android.resource";
|
||||
field public static final java.lang.String SCHEME_CONTENT = "content";
|
||||
field public static final java.lang.String SCHEME_FILE = "file";
|
||||
@@ -32669,7 +32674,10 @@ package android.provider {
|
||||
method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
|
||||
method public android.content.res.AssetFileDescriptor openTypedDocument(java.lang.String, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException;
|
||||
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
|
||||
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal);
|
||||
method public final android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal);
|
||||
method public abstract android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], java.lang.String) throws java.io.FileNotFoundException;
|
||||
method public android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], android.os.Bundle) throws java.io.FileNotFoundException;
|
||||
method public abstract android.database.Cursor queryDocument(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
|
||||
method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException;
|
||||
method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException;
|
||||
|
||||
@@ -915,7 +915,12 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
|
||||
|
||||
/**
|
||||
* Implement this to handle query requests from clients.
|
||||
* This method can be called from multiple threads, as described in
|
||||
*
|
||||
* <p>Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher should override
|
||||
* {@link #query(Uri, String[], Bundle, CancellationSignal)} and provide a stub
|
||||
* implementation of this method.
|
||||
*
|
||||
* <p>This method can be called from multiple threads, as described in
|
||||
* <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
|
||||
* and Threads</a>.
|
||||
* <p>
|
||||
@@ -974,7 +979,11 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
|
||||
|
||||
/**
|
||||
* Implement this to handle query requests from clients with support for cancellation.
|
||||
* This method can be called from multiple threads, as described in
|
||||
*
|
||||
* <p>Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher should override
|
||||
* {@link #query(Uri, String[], Bundle, CancellationSignal)} instead of this method.
|
||||
*
|
||||
* <p>This method can be called from multiple threads, as described in
|
||||
* <a href="{@docRoot}guide/topics/fundamentals/processes-and-threads.html#Threads">Processes
|
||||
* and Threads</a>.
|
||||
* <p>
|
||||
@@ -1048,9 +1057,9 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
|
||||
* {@link #query(Uri, String[], String, String[], String, CancellationSignal).
|
||||
*
|
||||
* <p>Traditional SQL arguments can be found in the bundle using the following keys:
|
||||
* <li>{@link ContentResolver#QUERY_ARG_SELECTION}
|
||||
* <li>{@link ContentResolver#QUERY_ARG_SELECTION_ARGS}
|
||||
* <li>{@link ContentResolver#QUERY_ARG_SORT_ORDER}
|
||||
* <li>{@link ContentResolver#QUERY_ARG_SQL_SELECTION}
|
||||
* <li>{@link ContentResolver#QUERY_ARG_SQL_SELECTION_ARGS}
|
||||
* <li>{@link ContentResolver#QUERY_ARG_SQL_SORT_ORDER}
|
||||
*
|
||||
* @see #query(Uri, String[], String, String[], String, CancellationSignal) for
|
||||
* implementation details.
|
||||
@@ -1071,12 +1080,21 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
|
||||
public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
|
||||
@Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) {
|
||||
queryArgs = queryArgs != null ? queryArgs : Bundle.EMPTY;
|
||||
|
||||
String sortClause = queryArgs.getString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER);
|
||||
|
||||
// if client didn't explicitly supply and sql sort order argument, we try to build
|
||||
// one from sort columns if present.
|
||||
if (sortClause == null && queryArgs.containsKey(ContentResolver.QUERY_ARG_SORT_COLUMNS)) {
|
||||
sortClause = ContentResolver.createSqlSortClause(queryArgs);
|
||||
}
|
||||
|
||||
return query(
|
||||
uri,
|
||||
projection,
|
||||
queryArgs.getString(ContentResolver.QUERY_ARG_SELECTION),
|
||||
queryArgs.getStringArray(ContentResolver.QUERY_ARG_SELECTION_ARGS),
|
||||
queryArgs.getString(ContentResolver.QUERY_ARG_SORT_ORDER),
|
||||
queryArgs.getString(ContentResolver.QUERY_ARG_SQL_SELECTION),
|
||||
queryArgs.getStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS),
|
||||
sortClause,
|
||||
cancellationSignal);
|
||||
}
|
||||
|
||||
|
||||
@@ -205,21 +205,111 @@ public abstract class ContentResolver {
|
||||
* Key for an SQL style selection string that may be present in the query Bundle argument
|
||||
* passed to {@link ContentProvider#query(Uri, String[], Bundle, CancellationSignal)}
|
||||
* when called by a legacy client.
|
||||
*
|
||||
* <p>Clients should never include user supplied values directly in the selection string,
|
||||
* as this presents an avenue for SQL injection attacks. In lieu of this, a client
|
||||
* should use standard placeholder notation to represent values in a selection string,
|
||||
* then supply a corresponding value in {@value #QUERY_ARG_SQL_SELECTION_ARGS}.
|
||||
*
|
||||
* <p><b>Clients targeting Android O or higher are strongly encourage to use structured
|
||||
* query arguments in lieu of opaque SQL query clauses.</b> See:
|
||||
* {@link #QUERY_ARG_SORT_COLUMNS}, {@link #QUERY_ARG_SORT_DIRECTION}, and
|
||||
* {@link #QUERY_ARG_SORT_COLLATION}.
|
||||
*/
|
||||
public static final String QUERY_ARG_SELECTION = "android:query-selection";
|
||||
public static final String QUERY_ARG_SQL_SELECTION = "android:query-sql-selection";
|
||||
|
||||
/**
|
||||
* Key for sql selection string arguments list.
|
||||
* @see #QUERY_ARG_SELECTION
|
||||
* Key for SQL selection string arguments list.
|
||||
*
|
||||
* <p>Clients should never include user supplied values directly in the selection string,
|
||||
* as this presents an avenue for SQL injection attacks. In lieu of this, a client
|
||||
* should use standard placeholder notation to represent values in a selection string,
|
||||
* then supply a corresponding value in {@value #QUERY_ARG_SQL_SELECTION_ARGS}.
|
||||
*
|
||||
* <p><b>Clients targeting Android O or higher are strongly encourage to use structured
|
||||
* query arguments in lieu of opaque SQL query clauses.</b> See:
|
||||
* {@link #QUERY_ARG_SORT_COLUMNS}, {@link #QUERY_ARG_SORT_DIRECTION}, and
|
||||
* {@link #QUERY_ARG_SORT_COLLATION}.
|
||||
*/
|
||||
public static final String QUERY_ARG_SELECTION_ARGS = "android:query-selection-args";
|
||||
public static final String QUERY_ARG_SQL_SELECTION_ARGS = "android:query-sql-selection-args";
|
||||
|
||||
/**
|
||||
* Key for an SQL style sort string that may be present in the query Bundle argument
|
||||
* passed to {@link ContentProvider#query(Uri, String[], Bundle, CancellationSignal)}
|
||||
* when called by a legacy client.
|
||||
*
|
||||
* <p><b>Clients targeting Android O or higher are strongly encourage to use structured
|
||||
* query arguments in lieu of opaque SQL query clauses.</b> See:
|
||||
* {@link #QUERY_ARG_SORT_COLUMNS}, {@link #QUERY_ARG_SORT_DIRECTION}, and
|
||||
* {@link #QUERY_ARG_SORT_COLLATION}.
|
||||
*/
|
||||
public static final String QUERY_ARG_SORT_ORDER = "android:query-sort-order";
|
||||
public static final String QUERY_ARG_SQL_SORT_ORDER = "android:query-sql-sort-order";
|
||||
|
||||
/**
|
||||
* Identifies the list columns against which to sort results.
|
||||
*
|
||||
* <p>Columns present in this list must also be included in the projection
|
||||
* supplied to {@link ContentResolver#query(Uri, String[], Bundle, CancellationSignal)}.
|
||||
*
|
||||
* <p>Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher are strongly
|
||||
* encouraged to include an entry in Cursor extras under this same key as an indication
|
||||
* to the client that column sorting was honored.
|
||||
*
|
||||
* <p>QUERY_SORT* values are exclusive from QUERY_ARG_SQL* arguments.
|
||||
* When any QUERY_SORT arguments are present, any QUERY_ARG_SQL* values will be ignored.
|
||||
*/
|
||||
public static final String QUERY_ARG_SORT_COLUMNS = "android:query-sort-columns";
|
||||
|
||||
/**
|
||||
* Specifies desired sort order. When unspecified a provider may provide a default
|
||||
* sort direction, or choose to return unsorted results.
|
||||
*
|
||||
* <p>Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher are strongly
|
||||
* encouraged to include an entry in Cursor extras under this same key as an indication
|
||||
* to the client that sort direction was honored.
|
||||
*
|
||||
* @see #QUERY_SORT_DIRECTION_ASCENDING
|
||||
* @see #QUERY_SORT_DIRECTION_DESCENDING
|
||||
*/
|
||||
public static final String QUERY_ARG_SORT_DIRECTION = "android:query-sort-direction";
|
||||
|
||||
/**
|
||||
* Allows client to specify a hint to the provider as to which collation
|
||||
* to use when sorting text values.
|
||||
*
|
||||
* <p>Providers may provide their own collators. When selecting a custom collator
|
||||
* the value will be determined by the Provider.
|
||||
*
|
||||
* <p>apps targeting {@link android.os.Build.VERSION_CODES#O} or higher are strongly
|
||||
* encouraged to include an entry in Cursor extras under this same key as an indication
|
||||
* to the client that collation was honored.
|
||||
*
|
||||
* @see #QUERY_COLLATOR_MODE_NOCASE
|
||||
*/
|
||||
public static final String QUERY_ARG_SORT_COLLATION = "android:query-sort-collation";
|
||||
|
||||
/** @hide */
|
||||
@IntDef(flag = false, value = {
|
||||
QUERY_SORT_DIRECTION_ASCENDING,
|
||||
QUERY_SORT_DIRECTION_DESCENDING
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface SortDirection {}
|
||||
public static final int QUERY_SORT_DIRECTION_ASCENDING = 0;
|
||||
public static final int QUERY_SORT_DIRECTION_DESCENDING = 1;
|
||||
|
||||
/**
|
||||
* @see {@link java.text.Collector} for details on respective collation strength.
|
||||
* @hide
|
||||
*/
|
||||
@IntDef(flag = false, value = {
|
||||
java.text.Collator.PRIMARY,
|
||||
java.text.Collator.SECONDARY,
|
||||
java.text.Collator.TERTIARY,
|
||||
java.text.Collator.IDENTICAL
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface QueryCollator {}
|
||||
|
||||
/**
|
||||
* This is the Android platform's base MIME type for a content: URI
|
||||
@@ -2685,8 +2775,8 @@ public abstract class ContentResolver {
|
||||
EventLogTags.CONTENT_QUERY_SAMPLE,
|
||||
uri.toString(),
|
||||
projectionBuffer.toString(),
|
||||
queryArgs.getString(QUERY_ARG_SELECTION, ""),
|
||||
queryArgs.getString(QUERY_ARG_SORT_ORDER, ""),
|
||||
queryArgs.getString(QUERY_ARG_SQL_SELECTION, ""),
|
||||
queryArgs.getString(QUERY_ARG_SQL_SORT_ORDER, ""),
|
||||
durationMillis,
|
||||
blockingPackage != null ? blockingPackage : "",
|
||||
samplePercent);
|
||||
@@ -2815,14 +2905,54 @@ public abstract class ContentResolver {
|
||||
|
||||
Bundle queryArgs = new Bundle();
|
||||
if (selection != null) {
|
||||
queryArgs.putString(QUERY_ARG_SELECTION, selection);
|
||||
queryArgs.putString(QUERY_ARG_SQL_SELECTION, selection);
|
||||
}
|
||||
if (selectionArgs != null) {
|
||||
queryArgs.putStringArray(QUERY_ARG_SELECTION_ARGS, selectionArgs);
|
||||
queryArgs.putStringArray(QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs);
|
||||
}
|
||||
if (sortOrder != null) {
|
||||
queryArgs.putString(QUERY_ARG_SORT_ORDER, sortOrder);
|
||||
queryArgs.putString(QUERY_ARG_SQL_SORT_ORDER, sortOrder);
|
||||
}
|
||||
return queryArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns structured sort args formatted as an SQL sort clause.
|
||||
*
|
||||
* Collator clauses are not included as column information is unknown, and
|
||||
* collate clauses should only be included on text fields.
|
||||
*
|
||||
* TODO: Should we explicitly validate that colums are present in the projection?
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static String createSqlSortClause(Bundle queryArgs) {
|
||||
String[] columns = queryArgs.getStringArray(QUERY_ARG_SORT_COLUMNS);
|
||||
if (columns == null || columns.length == 0) {
|
||||
throw new IllegalArgumentException("Can't create sort clause without columns.");
|
||||
}
|
||||
|
||||
String query = TextUtils.join(", ", columns);
|
||||
|
||||
switch (queryArgs.getInt(
|
||||
QUERY_ARG_SORT_DIRECTION, Integer.MIN_VALUE)) {
|
||||
case QUERY_SORT_DIRECTION_ASCENDING:
|
||||
query += " ASC";
|
||||
break;
|
||||
case QUERY_SORT_DIRECTION_DESCENDING:
|
||||
query += " DESC";
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported sort direction value."
|
||||
+ " See ContentResolver documentation for details.");
|
||||
}
|
||||
|
||||
// Interpret PRIMARY collation strength as no-case collation.
|
||||
int collation = queryArgs.getInt(
|
||||
ContentResolver.QUERY_ARG_SORT_COLLATION, java.text.Collator.IDENTICAL);
|
||||
if (collation == java.text.Collator.PRIMARY || collation == java.text.Collator.SECONDARY) {
|
||||
query += " COLLATE NOCASE";
|
||||
}
|
||||
return query;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ import android.graphics.Point;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
import android.os.OperationCanceledException;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.ParcelFileDescriptor.OnCloseListener;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
@@ -416,6 +417,9 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
* must only return immediate descendants, as additional queries will be
|
||||
* issued to recursively explore the tree.
|
||||
* <p>
|
||||
* Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher
|
||||
* should override {@link #queryChildDocuments(String, String[], Bundle)}.
|
||||
* <p>
|
||||
* If your provider is cloud-based, and you have some data cached or pinned
|
||||
* locally, you may return the local data immediately, setting
|
||||
* {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
|
||||
@@ -450,10 +454,53 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
String parentDocumentId, String[] projection, String sortOrder)
|
||||
throws FileNotFoundException;
|
||||
|
||||
/**
|
||||
* Override this method to return the children documents contained
|
||||
* in the requested directory. This must return immediate descendants only.
|
||||
*
|
||||
* <p>If your provider is cloud-based, and you have data cached
|
||||
* locally, you may return the local data immediately, setting
|
||||
* {@link DocumentsContract#EXTRA_LOADING} on Cursor extras to indicate that
|
||||
* you are still fetching additional data. Then, when the network data is
|
||||
* available, you can send a change notification to trigger a requery and
|
||||
* return the complete contents. To return a Cursor with extras, you need to
|
||||
* extend and override {@link Cursor#getExtras()}.
|
||||
*
|
||||
* <p>To support change notifications, you must
|
||||
* {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
|
||||
* Uri, such as
|
||||
* {@link DocumentsContract#buildChildDocumentsUri(String, String)}. Then
|
||||
* you can call {@link ContentResolver#notifyChange(Uri,
|
||||
* android.database.ContentObserver, boolean)} with that Uri to send change
|
||||
* notifications.
|
||||
*
|
||||
* @param parentDocumentId the directory to return children for.
|
||||
* @param projection list of {@link Document} columns to put into the
|
||||
* cursor. If {@code null} all supported columns should be
|
||||
* included.
|
||||
* @param queryArgs Bundle containing sorting information or other
|
||||
* argument useful to the provider. If no sorting
|
||||
* information is available, default sorting
|
||||
* will be used, which may be unordered. See
|
||||
* {@link ContentResolver#QUERY_ARG_SORT_COLUMNS} for
|
||||
* details.
|
||||
*
|
||||
* @see DocumentsContract#EXTRA_LOADING
|
||||
* @see DocumentsContract#EXTRA_INFO
|
||||
* @see DocumentsContract#EXTRA_ERROR
|
||||
*/
|
||||
public Cursor queryChildDocuments(
|
||||
String parentDocumentId, @Nullable String[] projection, @Nullable Bundle queryArgs)
|
||||
throws FileNotFoundException {
|
||||
|
||||
return queryChildDocuments(
|
||||
parentDocumentId, projection, getSortClause(queryArgs));
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
@SuppressWarnings("unused")
|
||||
public Cursor queryChildDocumentsForManage(
|
||||
String parentDocumentId, String[] projection, String sortOrder)
|
||||
String parentDocumentId, @Nullable String[] projection, @Nullable String sortOrder)
|
||||
throws FileNotFoundException {
|
||||
throw new UnsupportedOperationException("Manage not supported");
|
||||
}
|
||||
@@ -594,6 +641,22 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
throw new FileNotFoundException("The requested MIME type is not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Cursor query(Uri uri, String[] projection, String selection,
|
||||
String[] selectionArgs, String sortOrder) {
|
||||
// As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
|
||||
// transport method. We override that, and don't ever delegate to this method.
|
||||
throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Cursor query(Uri uri, String[] projection, String selection,
|
||||
String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
|
||||
// As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
|
||||
// transport method. We override that, and don't ever delegate to this metohd.
|
||||
throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation is provided by the parent class. Cannot be overriden.
|
||||
*
|
||||
@@ -604,8 +667,8 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
* @see #querySearchDocuments(String, String, String[])
|
||||
*/
|
||||
@Override
|
||||
public final Cursor query(Uri uri, String[] projection, String selection,
|
||||
String[] selectionArgs, String sortOrder) {
|
||||
public final Cursor query(
|
||||
Uri uri, String[] projection, Bundle queryArgs, CancellationSignal cancellationSignal) {
|
||||
try {
|
||||
switch (mMatcher.match(uri)) {
|
||||
case MATCH_ROOTS:
|
||||
@@ -623,10 +686,13 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
case MATCH_CHILDREN_TREE:
|
||||
enforceTree(uri);
|
||||
if (DocumentsContract.isManageMode(uri)) {
|
||||
// TODO: Update "ForManage" variant to support query args.
|
||||
return queryChildDocumentsForManage(
|
||||
getDocumentId(uri), projection, sortOrder);
|
||||
getDocumentId(uri),
|
||||
projection,
|
||||
getSortClause(queryArgs));
|
||||
} else {
|
||||
return queryChildDocuments(getDocumentId(uri), projection, sortOrder);
|
||||
return queryChildDocuments(getDocumentId(uri), projection, queryArgs);
|
||||
}
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unsupported Uri " + uri);
|
||||
@@ -637,6 +703,17 @@ public abstract class DocumentsProvider extends ContentProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private static @Nullable String getSortClause(@Nullable Bundle queryArgs) {
|
||||
queryArgs = queryArgs != null ? queryArgs : Bundle.EMPTY;
|
||||
String sortClause = queryArgs.getString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER);
|
||||
|
||||
if (sortClause == null && queryArgs.containsKey(ContentResolver.QUERY_ARG_SORT_COLUMNS)) {
|
||||
sortClause = ContentResolver.createSqlSortClause(queryArgs);
|
||||
}
|
||||
|
||||
return sortClause;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation is provided by the parent class. Cannot be overriden.
|
||||
*
|
||||
|
||||
@@ -670,7 +670,7 @@ public class ExternalStorageProvider extends DocumentsProvider {
|
||||
parent = mRoots.get(rootId).path;
|
||||
}
|
||||
|
||||
final LinkedList<File> pending = new LinkedList<File>();
|
||||
final LinkedList<File> pending = new LinkedList<>();
|
||||
pending.add(parent);
|
||||
while (!pending.isEmpty() && result.getCount() < 24) {
|
||||
final File file = pending.removeFirst();
|
||||
|
||||
@@ -162,7 +162,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
|
||||
assertEquals(0, openedDevice.length);
|
||||
}
|
||||
// Device is opened automatically when querying its children.
|
||||
try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) {}
|
||||
try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) {}
|
||||
|
||||
{
|
||||
final MtpDeviceRecord[] openedDevice = mProvider.getOpenedDeviceRecordsCache();
|
||||
@@ -412,7 +412,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
|
||||
.build()
|
||||
});
|
||||
|
||||
final Cursor cursor = mProvider.queryChildDocuments("1", null, null);
|
||||
final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null);
|
||||
assertEquals(1, cursor.getCount());
|
||||
|
||||
assertTrue(cursor.moveToNext());
|
||||
@@ -429,7 +429,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
|
||||
public void testQueryChildDocuments_cursorError() throws Exception {
|
||||
setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
|
||||
try {
|
||||
mProvider.queryChildDocuments("1", null, null);
|
||||
mProvider.queryChildDocuments("1", null, (String) null);
|
||||
fail();
|
||||
} catch (FileNotFoundException error) {}
|
||||
}
|
||||
@@ -438,7 +438,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
|
||||
setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
|
||||
setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
|
||||
mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
|
||||
try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) {
|
||||
try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) {
|
||||
assertEquals(0, cursor.getCount());
|
||||
assertFalse(cursor.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING));
|
||||
}
|
||||
@@ -590,7 +590,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
|
||||
assertEquals(1, cursor.getCount());
|
||||
}
|
||||
|
||||
try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) {
|
||||
try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) {
|
||||
assertEquals(0, cursor.getCount());
|
||||
assertEquals(
|
||||
"error_busy_device",
|
||||
@@ -611,7 +611,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
|
||||
assertEquals(1, cursor.getCount());
|
||||
}
|
||||
|
||||
try (final Cursor cursor = mProvider.queryChildDocuments("1", null, null)) {
|
||||
try (final Cursor cursor = mProvider.queryChildDocuments("1", null, (String) null)) {
|
||||
assertEquals(0, cursor.getCount());
|
||||
assertEquals(
|
||||
"error_locked_device",
|
||||
@@ -663,7 +663,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
|
||||
try (final Cursor cursor = mProvider.queryChildDocuments(
|
||||
String.valueOf(documentIdOffset + i),
|
||||
strings(Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME),
|
||||
null)) {
|
||||
(String) null)) {
|
||||
assertEquals(1, cursor.getCount());
|
||||
cursor.moveToNext();
|
||||
assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0));
|
||||
@@ -684,7 +684,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
|
||||
try (final Cursor cursor = mProvider.queryChildDocuments(
|
||||
String.valueOf(documentIdOffset + i),
|
||||
strings(Document.COLUMN_DOCUMENT_ID),
|
||||
null)) {
|
||||
(String) null)) {
|
||||
assertEquals(1, cursor.getCount());
|
||||
cursor.moveToNext();
|
||||
assertEquals(String.valueOf(documentIdOffset + i + 1), cursor.getString(0));
|
||||
@@ -758,7 +758,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
|
||||
mProvider.resumeRootScanner();
|
||||
mResolver.waitForNotification(ROOTS_URI, 1);
|
||||
try (final Cursor cursor = mProvider.queryChildDocuments(
|
||||
"1", strings(Document.COLUMN_DOCUMENT_ID), null)) {
|
||||
"1", strings(Document.COLUMN_DOCUMENT_ID), (String) null)) {
|
||||
assertEquals(1, cursor.getCount());
|
||||
cursor.moveToNext();
|
||||
assertEquals("3", cursor.getString(0));
|
||||
@@ -917,7 +917,9 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
|
||||
}
|
||||
mMtpManager.setObjectHandles(deviceId, storageId, parentHandle, handles);
|
||||
return getStrings(mProvider.queryChildDocuments(
|
||||
parentDocumentId, strings(DocumentsContract.Document.COLUMN_DOCUMENT_ID), null));
|
||||
parentDocumentId,
|
||||
strings(DocumentsContract.Document.COLUMN_DOCUMENT_ID),
|
||||
(String) null));
|
||||
}
|
||||
|
||||
static class HierarchyDocument {
|
||||
|
||||
Reference in New Issue
Block a user