Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 29c3f68c authored by Steve McKay's avatar Steve McKay
Browse files

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.
parent f5393c8a
Loading
Loading
Loading
Loading
+11 −3
Original line number Diff line number Diff line
@@ -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;
+11 −3
Original line number Diff line number Diff line
@@ -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;
+11 −3
Original line number Diff line number Diff line
@@ -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;
+26 −8
Original line number Diff line number Diff line
@@ -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);
    }

+140 −10
Original line number Diff line number Diff line
@@ -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_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
     */
    public static final String QUERY_ARG_SORT_ORDER = "android:query-sort-order";
    @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;
    }
}
Loading