OSDN Git Service

RESTRICT AUTOMERGE: Recover shady content:// paths.
[android-x86/frameworks-base.git] / core / java / android / content / ContentResolver.java
index daa1b93..9ccc552 100644 (file)
@@ -23,7 +23,7 @@ import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.TestApi;
 import android.annotation.UserIdInt;
-import android.app.ActivityManagerNative;
+import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.content.pm.PackageManager.NameNotFoundException;
@@ -194,6 +194,193 @@ public abstract class ContentResolver {
     public static final String EXTRA_SIZE = "android.content.extra.SIZE";
 
     /**
+     * An extra boolean describing whether a particular provider supports refresh
+     * or not. If a provider supports refresh, it should include this key in its
+     * returned Cursor as part of its query call.
+     *
+     */
+    public static final String EXTRA_REFRESH_SUPPORTED = "android.content.extra.REFRESH_SUPPORTED";
+
+    /**
+     * 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>Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher are strongly
+     * encourage to use structured query arguments in lieu of opaque SQL query clauses.</b>
+     *
+     * @see #QUERY_ARG_SORT_COLUMNS
+     * @see #QUERY_ARG_SORT_DIRECTION
+     * @see #QUERY_ARG_SORT_COLLATION
+     */
+    public static final String QUERY_ARG_SQL_SELECTION = "android:query-arg-sql-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>Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher are strongly
+     * encourage to use structured query arguments in lieu of opaque SQL query clauses.</b>
+     *
+     * @see #QUERY_ARG_SORT_COLUMNS
+     * @see #QUERY_ARG_SORT_DIRECTION
+     * @see #QUERY_ARG_SORT_COLLATION
+     */
+    public static final String QUERY_ARG_SQL_SELECTION_ARGS =
+            "android:query-arg-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>Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher are strongly
+     * encourage to use structured query arguments in lieu of opaque SQL query clauses.</b>
+     *
+     * @see #QUERY_ARG_SORT_COLUMNS
+     * @see #QUERY_ARG_SORT_DIRECTION
+     * @see #QUERY_ARG_SORT_COLLATION
+     */
+    public static final String QUERY_ARG_SQL_SORT_ORDER = "android:query-arg-sql-sort-order";
+
+    /**
+     * Specifies the list of columns against which to sort results. When first column values
+     * are identical, records are then sorted based on second column values, and so on.
+     *
+     * <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:
+     *
+     * <li>{@link ContentProvider} implementations: When preparing data in
+     * {@link ContentProvider#query(Uri, String[], Bundle, CancellationSignal)}, if sort columns
+     * is reflected in the returned Cursor, it is  strongly recommended that
+     * {@link #QUERY_ARG_SORT_COLUMNS} then be included in the array of honored arguments
+     * reflected in {@link Cursor} extras {@link Bundle} under {@link #EXTRA_HONORED_ARGS}.
+     *
+     * <li>When querying a provider, where no QUERY_ARG_SQL* otherwise exists in the
+     * arguments {@link Bundle}, the Content framework will attempt to synthesize
+     * an QUERY_ARG_SQL* argument using the corresponding QUERY_ARG_SORT* values.
+     */
+    public static final String QUERY_ARG_SORT_COLUMNS = "android:query-arg-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:
+     *
+     * <li>{@link ContentProvider} implementations: When preparing data in
+     * {@link ContentProvider#query(Uri, String[], Bundle, CancellationSignal)}, if sort direction
+     * is reflected in the returned Cursor, it is  strongly recommended that
+     * {@link #QUERY_ARG_SORT_DIRECTION} then be included in the array of honored arguments
+     * reflected in {@link Cursor} extras {@link Bundle} under {@link #EXTRA_HONORED_ARGS}.
+     *
+     * <li>When querying a provider, where no QUERY_ARG_SQL* otherwise exists in the
+     * arguments {@link Bundle}, the Content framework will attempt to synthesize
+     * a QUERY_ARG_SQL* argument using the corresponding QUERY_ARG_SORT* values.
+     *
+     * @see #QUERY_SORT_DIRECTION_ASCENDING
+     * @see #QUERY_SORT_DIRECTION_DESCENDING
+     */
+    public static final String QUERY_ARG_SORT_DIRECTION = "android:query-arg-sort-direction";
+
+    /**
+     * Allows client to specify a hint to the provider declaring which collation
+     * to use when sorting text values.
+     *
+     * <p>Providers may support custom collators. When specifying a custom collator
+     * the value is determined by the Provider.
+     *
+     * <li>{@link ContentProvider} implementations: When preparing data in
+     * {@link ContentProvider#query(Uri, String[], Bundle, CancellationSignal)}, if sort collation
+     * is reflected in the returned Cursor, it is  strongly recommended that
+     * {@link #QUERY_ARG_SORT_COLLATION} then be included in the array of honored arguments
+     * reflected in {@link Cursor} extras {@link Bundle} under {@link #EXTRA_HONORED_ARGS}.
+     *
+     * <li>When querying a provider, where no QUERY_ARG_SQL* otherwise exists in the
+     * arguments {@link Bundle}, the Content framework will attempt to synthesize
+     * a QUERY_ARG_SQL* argument using the corresponding QUERY_ARG_SORT* values.
+     *
+     * @see java.text.Collator#PRIMARY
+     * @see java.text.Collator#SECONDARY
+     * @see java.text.Collator#TERTIARY
+     * @see java.text.Collator#IDENTICAL
+     */
+    public static final String QUERY_ARG_SORT_COLLATION = "android:query-arg-sort-collation";
+
+    /**
+     * Allows provider to report back to client which query keys are honored in a Cursor.
+     *
+     * <p>Key identifying a {@code String[]} containing all QUERY_ARG_SORT* arguments
+     * honored by the provider. Include this in {@link Cursor} extras {@link Bundle}
+     * when any QUERY_ARG_SORT* value was honored during the preparation of the
+     * results {@link Cursor}.
+     *
+     * <p>If present, ALL honored arguments are enumerated in this extra’s payload.
+     *
+     * @see #QUERY_ARG_SORT_COLUMNS
+     * @see #QUERY_ARG_SORT_DIRECTION
+     * @see #QUERY_ARG_SORT_COLLATION
+     */
+    public static final String EXTRA_HONORED_ARGS = "android.content.extra.HONORED_ARGS";
+
+    /** @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 {}
+
+    /**
+     * Specifies the offset row index within a Cursor.
+     */
+    public static final String QUERY_ARG_OFFSET = "android:query-arg-offset";
+
+    /**
+     * Specifies the max number of rows to include in a Cursor.
+     */
+    public static final String QUERY_ARG_LIMIT = "android:query-arg-limit";
+
+    /**
+     * Added to {@link Cursor} extras {@link Bundle} to indicate total row count of
+     * recordset when paging is supported. Providers must include this when
+     * implementing paging support.
+     *
+     * <p>A provider may return -1 that row count of the recordset is unknown.
+     *
+     * <p>Providers having returned -1 in a previous query are recommended to
+     * send content change notification once (if) full recordset size becomes
+     * known.
+     */
+    public static final String EXTRA_TOTAL_COUNT = "android.content.extra.TOTAL_COUNT";
+
+    /**
      * This is the Android platform's base MIME type for a content: URI
      * containing a Cursor of a single item.  Applications should use this
      * as the base type along with their own sub-type of their content: URIs
@@ -328,6 +515,7 @@ public abstract class ContentResolver {
     public ContentResolver(Context context) {
         mContext = context != null ? context : ActivityThread.currentApplication();
         mPackageName = mContext.getOpPackageName();
+        mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
     }
 
     /** @hide */
@@ -388,7 +576,7 @@ public abstract class ContentResolver {
         }
 
         try {
-            String type = ActivityManagerNative.getDefault().getProviderMimeType(
+            String type = ActivityManager.getService().getProviderMimeType(
                     ContentProvider.getUriWithoutUserId(url), resolveUserId(url));
             return type;
         } catch (RemoteException e) {
@@ -508,10 +696,44 @@ public abstract class ContentResolver {
      * @return A Cursor object, which is positioned before the first entry, or null
      * @see Cursor
      */
-    public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
+    public final @Nullable Cursor query(@RequiresPermission.Read @NonNull Uri uri,
             @Nullable String[] projection, @Nullable String selection,
             @Nullable String[] selectionArgs, @Nullable String sortOrder,
             @Nullable CancellationSignal cancellationSignal) {
+        Bundle queryArgs = createSqlQueryBundle(selection, selectionArgs, sortOrder);
+        return query(uri, projection, queryArgs, cancellationSignal);
+    }
+
+    /**
+     * Query the given URI, returning a {@link Cursor} over the result set
+     * with support for cancellation.
+     *
+     * <p>For best performance, the caller should follow these guidelines:
+     *
+     * <li>Provide an explicit projection, to prevent reading data from storage
+     * that aren't going to be used.
+     *
+     * Provider must identify which QUERY_ARG_SORT* arguments were honored during
+     * the preparation of the result set by including the respective argument keys
+     * in the {@link Cursor} extras {@link Bundle}. See {@link #EXTRA_HONORED_ARGS}
+     * for details.
+     *
+     * @see #QUERY_ARG_SORT_COLUMNS, #QUERY_ARG_SORT_DIRECTION, #QUERY_ARG_SORT_COLLATION.
+     *
+     * @param uri The URI, using the content:// scheme, for the content to
+     *         retrieve.
+     * @param projection A list of which columns to return. Passing null will
+     *         return all columns, which is inefficient.
+     * @param queryArgs A Bundle containing any arguments to the query.
+     * @param cancellationSignal A signal to cancel the operation in progress, or null if none.
+     * If the operation is canceled, then {@link OperationCanceledException} will be thrown
+     * when the query is executed.
+     * @return A Cursor object, which is positioned before the first entry, or null
+     * @see Cursor
+     */
+    public final @Nullable Cursor query(final @RequiresPermission.Read @NonNull Uri uri,
+            @Nullable String[] projection, @Nullable Bundle queryArgs,
+            @Nullable CancellationSignal cancellationSignal) {
         Preconditions.checkNotNull(uri, "uri");
         IContentProvider unstableProvider = acquireUnstableProvider(uri);
         if (unstableProvider == null) {
@@ -530,7 +752,7 @@ public abstract class ContentResolver {
             }
             try {
                 qCursor = unstableProvider.query(mPackageName, uri, projection,
-                        selection, selectionArgs, sortOrder, remoteCancellationSignal);
+                        queryArgs, remoteCancellationSignal);
             } catch (DeadObjectException e) {
                 // The remote process has died...  but we only hold an unstable
                 // reference though, so we might recover!!!  Let's try!!!!
@@ -540,8 +762,8 @@ public abstract class ContentResolver {
                 if (stableProvider == null) {
                     return null;
                 }
-                qCursor = stableProvider.query(mPackageName, uri, projection,
-                        selection, selectionArgs, sortOrder, remoteCancellationSignal);
+                qCursor = stableProvider.query(
+                        mPackageName, uri, projection, queryArgs, remoteCancellationSignal);
             }
             if (qCursor == null) {
                 return null;
@@ -550,7 +772,7 @@ public abstract class ContentResolver {
             // Force query execution.  Might fail and throw a runtime exception here.
             qCursor.getCount();
             long durationMillis = SystemClock.uptimeMillis() - startTime;
-            maybeLogQueryToEventLog(durationMillis, uri, projection, selection, sortOrder);
+            maybeLogQueryToEventLog(durationMillis, uri, projection, queryArgs);
 
             // Wrap the cursor object into CursorWrapperInner object.
             final IContentProvider provider = (stableProvider != null) ? stableProvider
@@ -664,6 +886,47 @@ public abstract class ContentResolver {
     }
 
     /**
+     * This allows clients to request an explicit refresh of content identified by {@code uri}.
+     * <p>
+     * Client code should only invoke this method when there is a strong indication (such as a user
+     * initiated pull to refresh gesture) that the content is stale.
+     * <p>
+     *
+     * @param url The Uri identifying the data to refresh.
+     * @param args Additional options from the client. The definitions of these are specific to the
+     *            content provider being called.
+     * @param cancellationSignal A signal to cancel the operation in progress, or {@code null} if
+     *            none. For example, if you called refresh on a particular uri, you should call
+     *            {@link CancellationSignal#throwIfCanceled()} to check whether the client has
+     *            canceled the refresh request.
+     * @return true if the provider actually tried refreshing.
+     */
+    public final boolean refresh(@NonNull Uri url, @Nullable Bundle args,
+            @Nullable CancellationSignal cancellationSignal) {
+        Preconditions.checkNotNull(url, "url");
+        IContentProvider provider = acquireProvider(url);
+        if (provider == null) {
+            return false;
+        }
+
+        try {
+            ICancellationSignal remoteCancellationSignal = null;
+            if (cancellationSignal != null) {
+                cancellationSignal.throwIfCanceled();
+                remoteCancellationSignal = provider.createCancellationSignal();
+                cancellationSignal.setRemote(remoteCancellationSignal);
+            }
+            return provider.refresh(mPackageName, url, args, remoteCancellationSignal);
+        } catch (RemoteException e) {
+            // Arbitrary and not worth documenting, as Activity
+            // Manager will kill this process shortly anyway.
+            return false;
+        } finally {
+            releaseProvider(provider);
+        }
+    }
+
+    /**
      * Open a stream on to the content associated with a content URI.  If there
      * is no data associated with the URI, FileNotFoundException is thrown.
      *
@@ -1628,13 +1891,18 @@ public abstract class ContentResolver {
     /**
      * Register an observer class that gets callbacks when data identified by a
      * given content URI changes.
-     *
-     * @param uri The URI to watch for changes. This can be a specific row URI, or a base URI
-     * for a whole class of content.
-     * @param notifyForDescendants When false, the observer will be notified whenever a
-     * change occurs to the exact URI specified by <code>uri</code> or to one of the
-     * URI's ancestors in the path hierarchy.  When true, the observer will also be notified
-     * whenever a change occurs to the URI's descendants in the path hierarchy.
+     * <p>
+     * Starting in {@link android.os.Build.VERSION_CODES#O}, all content
+     * notifications must be backed by a valid {@link ContentProvider}.
+     *
+     * @param uri The URI to watch for changes. This can be a specific row URI,
+     *            or a base URI for a whole class of content.
+     * @param notifyForDescendants When false, the observer will be notified
+     *            whenever a change occurs to the exact URI specified by
+     *            <code>uri</code> or to one of the URI's ancestors in the path
+     *            hierarchy. When true, the observer will also be notified
+     *            whenever a change occurs to the URI's descendants in the path
+     *            hierarchy.
      * @param observer The object that receives callbacks when changes occur.
      * @see #unregisterContentObserver
      */
@@ -1646,7 +1914,7 @@ public abstract class ContentResolver {
                 ContentProvider.getUriWithoutUserId(uri),
                 notifyForDescendants,
                 observer,
-                ContentProvider.getUserIdFromUri(uri, UserHandle.myUserId()));
+                ContentProvider.getUserIdFromUri(uri, mContext.getUserId()));
     }
 
     /** @hide - designated user version */
@@ -1654,7 +1922,7 @@ public abstract class ContentResolver {
             ContentObserver observer, @UserIdInt int userHandle) {
         try {
             getContentService().registerContentObserver(uri, notifyForDescendents,
-                    observer.getContentObserver(), userHandle);
+                    observer.getContentObserver(), userHandle, mTargetSdkVersion);
         } catch (RemoteException e) {
         }
     }
@@ -1678,16 +1946,22 @@ public abstract class ContentResolver {
     }
 
     /**
-     * Notify registered observers that a row was updated and attempt to sync changes
-     * to the network.
-     * To register, call {@link #registerContentObserver(android.net.Uri , boolean, android.database.ContentObserver) registerContentObserver()}.
-     * By default, CursorAdapter objects will get this notification.
+     * Notify registered observers that a row was updated and attempt to sync
+     * changes to the network.
+     * <p>
+     * To observe events sent through this call, use
+     * {@link #registerContentObserver(Uri, boolean, ContentObserver)}.
+     * <p>
+     * Starting in {@link android.os.Build.VERSION_CODES#O}, all content
+     * notifications must be backed by a valid {@link ContentProvider}.
      *
      * @param uri The uri of the content that was changed.
-     * @param observer The observer that originated the change, may be <code>null</null>.
-     * The observer that originated the change will only receive the notification if it
-     * has requested to receive self-change notifications by implementing
-     * {@link ContentObserver#deliverSelfNotifications()} to return true.
+     * @param observer The observer that originated the change, may be
+     *            <code>null</null>. The observer that originated the change
+     *            will only receive the notification if it has requested to
+     *            receive self-change notifications by implementing
+     *            {@link ContentObserver#deliverSelfNotifications()} to return
+     *            true.
      */
     public void notifyChange(@NonNull Uri uri, @Nullable ContentObserver observer) {
         notifyChange(uri, observer, true /* sync to network */);
@@ -1695,17 +1969,25 @@ public abstract class ContentResolver {
 
     /**
      * Notify registered observers that a row was updated.
-     * To register, call {@link #registerContentObserver(android.net.Uri , boolean, android.database.ContentObserver) registerContentObserver()}.
-     * By default, CursorAdapter objects will get this notification.
-     * If syncToNetwork is true, this will attempt to schedule a local sync using the sync
-     * adapter that's registered for the authority of the provided uri. No account will be
-     * passed to the sync adapter, so all matching accounts will be synchronized.
+     * <p>
+     * To observe events sent through this call, use
+     * {@link #registerContentObserver(Uri, boolean, ContentObserver)}.
+     * <p>
+     * If syncToNetwork is true, this will attempt to schedule a local sync
+     * using the sync adapter that's registered for the authority of the
+     * provided uri. No account will be passed to the sync adapter, so all
+     * matching accounts will be synchronized.
+     * <p>
+     * Starting in {@link android.os.Build.VERSION_CODES#O}, all content
+     * notifications must be backed by a valid {@link ContentProvider}.
      *
      * @param uri The uri of the content that was changed.
-     * @param observer The observer that originated the change, may be <code>null</null>.
-     * The observer that originated the change will only receive the notification if it
-     * has requested to receive self-change notifications by implementing
-     * {@link ContentObserver#deliverSelfNotifications()} to return true.
+     * @param observer The observer that originated the change, may be
+     *            <code>null</null>. The observer that originated the change
+     *            will only receive the notification if it has requested to
+     *            receive self-change notifications by implementing
+     *            {@link ContentObserver#deliverSelfNotifications()} to return
+     *            true.
      * @param syncToNetwork If true, same as {@link #NOTIFY_SYNC_TO_NETWORK}.
      * @see #requestSync(android.accounts.Account, String, android.os.Bundle)
      */
@@ -1716,22 +1998,30 @@ public abstract class ContentResolver {
                 ContentProvider.getUriWithoutUserId(uri),
                 observer,
                 syncToNetwork,
-                ContentProvider.getUserIdFromUri(uri, UserHandle.myUserId()));
+                ContentProvider.getUserIdFromUri(uri, mContext.getUserId()));
     }
 
     /**
      * Notify registered observers that a row was updated.
-     * To register, call {@link #registerContentObserver(android.net.Uri, boolean, android.database.ContentObserver) registerContentObserver()}.
-     * By default, CursorAdapter objects will get this notification.
-     * If syncToNetwork is true, this will attempt to schedule a local sync using the sync
-     * adapter that's registered for the authority of the provided uri. No account will be
-     * passed to the sync adapter, so all matching accounts will be synchronized.
+     * <p>
+     * To observe events sent through this call, use
+     * {@link #registerContentObserver(Uri, boolean, ContentObserver)}.
+     * <p>
+     * If syncToNetwork is true, this will attempt to schedule a local sync
+     * using the sync adapter that's registered for the authority of the
+     * provided uri. No account will be passed to the sync adapter, so all
+     * matching accounts will be synchronized.
+     * <p>
+     * Starting in {@link android.os.Build.VERSION_CODES#O}, all content
+     * notifications must be backed by a valid {@link ContentProvider}.
      *
      * @param uri The uri of the content that was changed.
-     * @param observer The observer that originated the change, may be <code>null</null>.
-     * The observer that originated the change will only receive the notification if it
-     * has requested to receive self-change notifications by implementing
-     * {@link ContentObserver#deliverSelfNotifications()} to return true.
+     * @param observer The observer that originated the change, may be
+     *            <code>null</null>. The observer that originated the change
+     *            will only receive the notification if it has requested to
+     *            receive self-change notifications by implementing
+     *            {@link ContentObserver#deliverSelfNotifications()} to return
+     *            true.
      * @param flags Additional flags: {@link #NOTIFY_SYNC_TO_NETWORK}.
      * @see #requestSync(android.accounts.Account, String, android.os.Bundle)
      */
@@ -1742,7 +2032,7 @@ public abstract class ContentResolver {
                 ContentProvider.getUriWithoutUserId(uri),
                 observer,
                 flags,
-                ContentProvider.getUserIdFromUri(uri, UserHandle.myUserId()));
+                ContentProvider.getUserIdFromUri(uri, mContext.getUserId()));
     }
 
     /**
@@ -1757,7 +2047,7 @@ public abstract class ContentResolver {
                     uri, observer == null ? null : observer.getContentObserver(),
                     observer != null && observer.deliverSelfNotifications(),
                     syncToNetwork ? NOTIFY_SYNC_TO_NETWORK : 0,
-                    userHandle);
+                    userHandle, mTargetSdkVersion);
         } catch (RemoteException e) {
         }
     }
@@ -1773,7 +2063,7 @@ public abstract class ContentResolver {
             getContentService().notifyChange(
                     uri, observer == null ? null : observer.getContentObserver(),
                     observer != null && observer.deliverSelfNotifications(), flags,
-                    userHandle);
+                    userHandle, mTargetSdkVersion);
         } catch (RemoteException e) {
         }
     }
@@ -1792,7 +2082,7 @@ public abstract class ContentResolver {
             @Intent.AccessUriMode int modeFlags) {
         Preconditions.checkNotNull(uri, "uri");
         try {
-            ActivityManagerNative.getDefault().takePersistableUriPermission(
+            ActivityManager.getService().takePersistableUriPermission(
                     ContentProvider.getUriWithoutUserId(uri), modeFlags, resolveUserId(uri));
         } catch (RemoteException e) {
         }
@@ -1810,7 +2100,7 @@ public abstract class ContentResolver {
             @Intent.AccessUriMode int modeFlags) {
         Preconditions.checkNotNull(uri, "uri");
         try {
-            ActivityManagerNative.getDefault().releasePersistableUriPermission(
+            ActivityManager.getService().releasePersistableUriPermission(
                     ContentProvider.getUriWithoutUserId(uri), modeFlags, resolveUserId(uri));
         } catch (RemoteException e) {
         }
@@ -1828,7 +2118,7 @@ public abstract class ContentResolver {
      */
     public @NonNull List<UriPermission> getPersistedUriPermissions() {
         try {
-            return ActivityManagerNative.getDefault()
+            return ActivityManager.getService()
                     .getPersistedUriPermissions(mPackageName, true).getList();
         } catch (RemoteException e) {
             throw new RuntimeException("Activity manager has died", e);
@@ -1844,7 +2134,7 @@ public abstract class ContentResolver {
      */
     public @NonNull List<UriPermission> getOutgoingPersistedUriPermissions() {
         try {
-            return ActivityManagerNative.getDefault()
+            return ActivityManager.getService()
                     .getPersistedUriPermissions(mPackageName, false).getList();
         } catch (RemoteException e) {
             throw new RuntimeException("Activity manager has died", e);
@@ -2491,6 +2781,7 @@ public abstract class ContentResolver {
         }
         try {
             ISyncStatusObserver.Stub observer = new ISyncStatusObserver.Stub() {
+                @Override
                 public void onStatusChanged(int which) throws RemoteException {
                     callback.onStatusChanged(which);
                 }
@@ -2540,6 +2831,11 @@ public abstract class ContentResolver {
         }
     }
 
+    /** {@hide} */
+    public int getTargetSdkVersion() {
+        return mTargetSdkVersion;
+    }
+
     /**
      * Returns sampling percentage for a given duration.
      *
@@ -2552,9 +2848,8 @@ public abstract class ContentResolver {
         return (int) (100 * durationMillis / SLOW_THRESHOLD_MILLIS) + 1;
     }
 
-    private void maybeLogQueryToEventLog(long durationMillis,
-                                         Uri uri, String[] projection,
-                                         String selection, String sortOrder) {
+    private void maybeLogQueryToEventLog(
+            long durationMillis, Uri uri, String[] projection, @Nullable Bundle queryArgs) {
         if (!ENABLE_CONTENT_SAMPLE) return;
         int samplePercent = samplePercentForDuration(durationMillis);
         if (samplePercent < 100) {
@@ -2565,6 +2860,9 @@ public abstract class ContentResolver {
             }
         }
 
+        // Ensure a non-null bundle.
+        queryArgs = (queryArgs != null) ? queryArgs : Bundle.EMPTY;
+
         StringBuilder projectionBuffer = new StringBuilder(100);
         if (projection != null) {
             for (int i = 0; i < projection.length; ++i) {
@@ -2586,8 +2884,8 @@ public abstract class ContentResolver {
             EventLogTags.CONTENT_QUERY_SAMPLE,
             uri.toString(),
             projectionBuffer.toString(),
-            selection != null ? selection : "",
-            sortOrder != null ? sortOrder : "",
+            queryArgs.getString(QUERY_ARG_SQL_SELECTION, ""),
+            queryArgs.getString(QUERY_ARG_SQL_SORT_ORDER, ""),
             durationMillis,
             blockingPackage != null ? blockingPackage : "",
             samplePercent);
@@ -2640,7 +2938,10 @@ public abstract class ContentResolver {
         @Override
         protected void finalize() throws Throwable {
             try {
-                mCloseGuard.warnIfOpen();
+                if (mCloseGuard != null) {
+                    mCloseGuard.warnIfOpen();
+                }
+
                 close();
             } finally {
                 super.finalize();
@@ -2689,6 +2990,7 @@ public abstract class ContentResolver {
     private final Context mContext;
 
     final String mPackageName;
+    final int mTargetSdkVersion;
 
     private static final String TAG = "ContentResolver";
 
@@ -2701,4 +3003,78 @@ public abstract class ContentResolver {
     public Drawable getTypeDrawable(String mimeType) {
         return MimeIconUtils.loadMimeIcon(mContext, mimeType);
     }
+
+    /**
+     * @hide
+     */
+    public static @Nullable Bundle createSqlQueryBundle(
+            @Nullable String selection,
+            @Nullable String[] selectionArgs,
+            @Nullable String sortOrder) {
+
+        if (selection == null && selectionArgs == null && sortOrder == null) {
+            return null;
+        }
+
+        Bundle queryArgs = new Bundle();
+        if (selection != null) {
+            queryArgs.putString(QUERY_ARG_SQL_SELECTION, selection);
+        }
+        if (selectionArgs != null) {
+            queryArgs.putStringArray(QUERY_ARG_SQL_SELECTION_ARGS, selectionArgs);
+        }
+        if (sortOrder != null) {
+            queryArgs.putString(QUERY_ARG_SQL_SORT_ORDER, sortOrder);
+        }
+        return queryArgs;
+    }
+
+    /**
+     * Returns structured sort args formatted as an SQL sort clause.
+     *
+     * NOTE: Collator clauses are suitable for use with non text fields. We might
+     * choose to omit any collation clause since we don't know the underlying
+     * type of data to be collated. Imperical testing shows that sqlite3 doesn't
+     * appear to care much about the presence of collate clauses in queries
+     * when ordering by numeric fields. For this reason we include collate
+     * clause unilaterally when {@link #QUERY_ARG_SORT_COLLATION} is present
+     * in query args bundle.
+     *
+     * TODO: Would be nice to explicitly validate that colums referenced in
+     * {@link #QUERY_ARG_SORT_COLUMNS} are present in the associated 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);
+
+        // Interpret PRIMARY and SECONDARY collation strength as no-case collation based
+        // on their javadoc descriptions.
+        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";
+        }
+
+        int sortDir = queryArgs.getInt(QUERY_ARG_SORT_DIRECTION, Integer.MIN_VALUE);
+        if (sortDir != Integer.MIN_VALUE) {
+            switch (sortDir) {
+                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.");
+            }
+        }
+        return query;
+    }
 }