diff options
15 files changed, 251 insertions, 89 deletions
diff --git a/api/current.txt b/api/current.txt index 5a2df57e2327..ce3c67d3643f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -7978,6 +7978,7 @@ package android.content { method public android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public abstract android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal); + method public android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal); method public boolean refresh(android.net.Uri, android.os.Bundle, android.os.CancellationSignal); method protected final void setPathPermissions(android.content.pm.PathPermission[]); method protected final void setReadPermission(java.lang.String); @@ -8010,6 +8011,7 @@ package android.content { method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException; method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) throws android.os.RemoteException; method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal) throws android.os.RemoteException; + method public android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal) throws android.os.RemoteException; method public deprecated boolean release(); method public final android.net.Uri uncanonicalize(android.net.Uri) throws android.os.RemoteException; method public int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]) throws android.os.RemoteException; @@ -8112,6 +8114,7 @@ package android.content { method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, 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 final boolean refresh(android.net.Uri, android.os.Bundle, android.os.CancellationSignal); method public final void registerContentObserver(android.net.Uri, boolean, android.database.ContentObserver); method public void releasePersistableUriPermission(android.net.Uri, int); @@ -8134,6 +8137,9 @@ 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 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"; diff --git a/api/system-current.txt b/api/system-current.txt index 7a287df833f6..2316ebe20c42 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -8313,6 +8313,7 @@ package android.content { method public android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public abstract android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal); + method public android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal); method public boolean refresh(android.net.Uri, android.os.Bundle, android.os.CancellationSignal); method protected final void setPathPermissions(android.content.pm.PathPermission[]); method protected final void setReadPermission(java.lang.String); @@ -8345,6 +8346,7 @@ package android.content { method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException; method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) throws android.os.RemoteException; method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal) throws android.os.RemoteException; + method public android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal) throws android.os.RemoteException; method public deprecated boolean release(); method public final android.net.Uri uncanonicalize(android.net.Uri) throws android.os.RemoteException; method public int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]) throws android.os.RemoteException; @@ -8447,6 +8449,7 @@ package android.content { method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, 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 final boolean refresh(android.net.Uri, android.os.Bundle, android.os.CancellationSignal); method public final void registerContentObserver(android.net.Uri, boolean, android.database.ContentObserver); method public void releasePersistableUriPermission(android.net.Uri, int); @@ -8469,6 +8472,9 @@ 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 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"; diff --git a/api/test-current.txt b/api/test-current.txt index 20b723da4db2..6a7930907daa 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -8000,6 +8000,7 @@ package android.content { method public android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public abstract android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal); + method public android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal); method public boolean refresh(android.net.Uri, android.os.Bundle, android.os.CancellationSignal); method protected final void setPathPermissions(android.content.pm.PathPermission[]); method protected final void setReadPermission(java.lang.String); @@ -8032,6 +8033,7 @@ package android.content { method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException, android.os.RemoteException; method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) throws android.os.RemoteException; method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal) throws android.os.RemoteException; + method public android.database.Cursor query(android.net.Uri, java.lang.String[], android.os.Bundle, android.os.CancellationSignal) throws android.os.RemoteException; method public deprecated boolean release(); method public final android.net.Uri uncanonicalize(android.net.Uri) throws android.os.RemoteException; method public int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]) throws android.os.RemoteException; @@ -8135,6 +8137,7 @@ package android.content { method public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(android.net.Uri, 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 final boolean refresh(android.net.Uri, android.os.Bundle, android.os.CancellationSignal); method public final void registerContentObserver(android.net.Uri, boolean, android.database.ContentObserver); method public void releasePersistableUriPermission(android.net.Uri, int); @@ -8157,6 +8160,9 @@ 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 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"; diff --git a/cmds/content/src/com/android/commands/content/Content.java b/cmds/content/src/com/android/commands/content/Content.java index 63641a8e38f4..3687f10f9974 100644 --- a/cmds/content/src/com/android/commands/content/Content.java +++ b/cmds/content/src/com/android/commands/content/Content.java @@ -19,6 +19,7 @@ package com.android.commands.content; import android.app.ActivityManager; import android.app.ContentProviderHolder; import android.app.IActivityManager; +import android.content.ContentResolver; import android.content.ContentValues; import android.content.IContentProvider; import android.database.Cursor; @@ -589,8 +590,8 @@ public class Content { @Override public void onExecute(IContentProvider provider) throws Exception { - Cursor cursor = provider.query(resolveCallingPackage(), mUri, mProjection, mWhere, - null, mSortOrder, null); + Cursor cursor = provider.query(resolveCallingPackage(), mUri, mProjection, + ContentResolver.createSqlQueryBundle(mWhere, null, mSortOrder), null); if (cursor == null) { System.out.println("No result found."); return; diff --git a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java index 32b45953fc77..b0ab2355adb6 100644 --- a/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java +++ b/cmds/uiautomator/library/testrunner-src/com/android/uiautomator/core/ShellUiAutomatorBridge.java @@ -20,6 +20,7 @@ import android.app.ActivityManager; import android.app.ContentProviderHolder; import android.app.IActivityManager; import android.app.UiAutomation; +import android.content.ContentResolver; import android.content.Context; import android.content.IContentProvider; import android.database.Cursor; @@ -69,10 +70,12 @@ public class ShellUiAutomatorBridge extends UiAutomatorBridge { cursor = provider.query(null, Settings.Secure.CONTENT_URI, new String[] { Settings.Secure.VALUE - }, "name=?", - new String[] { - Settings.Secure.LONG_PRESS_TIMEOUT - }, null, null); + }, + ContentResolver.createSqlQueryBundle( + "name=?", + new String[] { Settings.Secure.LONG_PRESS_TIMEOUT }, + null), + null); if (cursor.moveToFirst()) { longPressTimeout = cursor.getInt(0); } diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index f36940958011..cda98e5b4c79 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -97,6 +97,7 @@ import java.util.Arrays; * developer guide.</p> */ public abstract class ContentProvider implements ComponentCallbacks2 { + private static final String TAG = "ContentProvider"; /* @@ -118,7 +119,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { private boolean mNoPerms; private boolean mSingleUser; - private final ThreadLocal<String> mCallingPackage = new ThreadLocal<String>(); + private final ThreadLocal<String> mCallingPackage = new ThreadLocal<>(); private Transport mTransport = new Transport(); @@ -205,9 +206,8 @@ public abstract class ContentProvider implements ComponentCallbacks2 { } @Override - public Cursor query(String callingPkg, Uri uri, String[] projection, - String selection, String[] selectionArgs, String sortOrder, - ICancellationSignal cancellationSignal) { + public Cursor query(String callingPkg, Uri uri, @Nullable String[] projection, + @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) { validateIncomingUri(uri); uri = maybeGetUriWithoutUserId(uri); if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { @@ -225,9 +225,9 @@ public abstract class ContentProvider implements ComponentCallbacks2 { // However, the caller may be expecting to access them my index. Hence, // we have to execute the query as if allowed to get a cursor with the // columns. We then use the column names to return an empty cursor. - Cursor cursor = ContentProvider.this.query(uri, projection, selection, - selectionArgs, sortOrder, CancellationSignal.fromTransport( - cancellationSignal)); + Cursor cursor = ContentProvider.this.query( + uri, projection, queryArgs, + CancellationSignal.fromTransport(cancellationSignal)); if (cursor == null) { return null; } @@ -238,7 +238,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { final String original = setCallingPackage(callingPkg); try { return ContentProvider.this.query( - uri, projection, selection, selectionArgs, sortOrder, + uri, projection, queryArgs, CancellationSignal.fromTransport(cancellationSignal)); } finally { setCallingPackage(original); @@ -893,6 +893,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * (Content providers do not usually care about things like screen * orientation, but may want to know about locale changes.) */ + @Override public void onConfigurationChanged(Configuration newConfig) { } @@ -904,9 +905,11 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * <p>The default content provider implementation does nothing. * Subclasses may override this method to take appropriate action. */ + @Override public void onLowMemory() { } + @Override public void onTrimMemory(int level) { } @@ -1039,6 +1042,45 @@ public abstract class ContentProvider implements ComponentCallbacks2 { } /** + * Implement this to handle query requests where the arguments are packed into a {@link Bundle}. + * Arguments may include traditional SQL style query arguments. When present these + * should be handled according to the contract established in + * {@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} + * + * @see #query(Uri, String[], String, String[], String, CancellationSignal) for + * implementation details. + * + * @param uri The URI to query. This will be the full URI sent by the client. + * TODO: Me wonders about this use case, and how we adapt it. + * If the client is requesting a specific record, the URI will end + * in a record number that the implementation should parse and add + * to a WHERE or HAVING clause, specifying that _id value. + * @param projection The list of columns to put into the cursor. + * If {@code null} provide a default set of columns. + * @param queryArgs A Bundle containing all additional information necessary for the query. + * Values in the Bundle may include SQL style arguments. + * @param cancellationSignal A signal to cancel the operation in progress, + * or {@code null}. + * @return a Cursor or {@code null}. + */ + public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection, + @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) { + queryArgs = queryArgs != null ? queryArgs : Bundle.EMPTY; + return query( + uri, + projection, + queryArgs.getString(ContentResolver.QUERY_ARG_SELECTION), + queryArgs.getStringArray(ContentResolver.QUERY_ARG_SELECTION_ARGS), + queryArgs.getString(ContentResolver.QUERY_ARG_SORT_ORDER), + cancellationSignal); + } + + /** * Implement this to handle requests for the MIME type of the data at the * given URI. The returned MIME type should start with * <code>vnd.android.cursor.item</code> for a single record, @@ -1412,7 +1454,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * no file associated with the given URI or the mode is invalid. * @throws SecurityException Throws SecurityException if the caller does * not have permission to access the file. - * + * * @see #openFile(Uri, String) * @see #openFileHelper(Uri, String) * @see #getType(android.net.Uri) @@ -1851,7 +1893,7 @@ public abstract class ContentProvider implements ComponentCallbacks2 { /** * Implement this to shut down the ContentProvider instance. You can then * invoke this method in unit tests. - * + * * <p> * Android normally handles ContentProvider startup and shutdown * automatically. You do not need to start up or shut down a diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java index fd6cddbdde4c..732666f879b9 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -128,11 +128,20 @@ public class ContentProviderClient implements AutoCloseable { } /** See {@link ContentProvider#query ContentProvider.query} */ - public @Nullable Cursor query(@NonNull Uri url, @Nullable String[] projection, + public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) throws RemoteException { - Preconditions.checkNotNull(url, "url"); + Bundle queryArgs = + ContentResolver.createSqlQueryBundle(selection, selectionArgs, sortOrder); + return query(uri, projection, queryArgs, cancellationSignal); + } + + /** See {@link ContentProvider#query ContentProvider.query} */ + public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection, + Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) + throws RemoteException { + Preconditions.checkNotNull(uri, "url"); beforeRemote(); try { @@ -142,8 +151,8 @@ public class ContentProviderClient implements AutoCloseable { remoteCancellationSignal = mContentProvider.createCancellationSignal(); cancellationSignal.setRemote(remoteCancellationSignal); } - final Cursor cursor = mContentProvider.query(mPackageName, url, projection, selection, - selectionArgs, sortOrder, remoteCancellationSignal); + final Cursor cursor = mContentProvider.query( + mPackageName, uri, projection, queryArgs, remoteCancellationSignal); if (cursor == null) { return null; } diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index eadc013db16e..d428a3a857b7 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -16,6 +16,7 @@ package android.content; +import android.annotation.Nullable; import android.content.res.AssetFileDescriptor; import android.database.BulkCursorDescriptor; import android.database.BulkCursorToCursorAdaptor; @@ -92,25 +93,13 @@ abstract public class ContentProviderNative extends Binder implements IContentPr } } - // String selection, String[] selectionArgs... - String selection = data.readString(); - num = data.readInt(); - String[] selectionArgs = null; - if (num > 0) { - selectionArgs = new String[num]; - for (int i = 0; i < num; i++) { - selectionArgs[i] = data.readString(); - } - } - - String sortOrder = data.readString(); + Bundle queryArgs = data.readBundle(); IContentObserver observer = IContentObserver.Stub.asInterface( data.readStrongBinder()); ICancellationSignal cancellationSignal = ICancellationSignal.Stub.asInterface( data.readStrongBinder()); - Cursor cursor = query(callingPkg, url, projection, selection, selectionArgs, - sortOrder, cancellationSignal); + Cursor cursor = query(callingPkg, url, projection, queryArgs, cancellationSignal); if (cursor != null) { CursorToBulkCursorAdaptor adaptor = null; @@ -185,7 +174,7 @@ abstract public class ContentProviderNative extends Binder implements IContentPr String callingPkg = data.readString(); final int numOperations = data.readInt(); final ArrayList<ContentProviderOperation> operations = - new ArrayList<ContentProviderOperation>(numOperations); + new ArrayList<>(numOperations); for (int i = 0; i < numOperations; i++) { operations.add(i, ContentProviderOperation.CREATOR.createFromParcel(data)); } @@ -378,6 +367,7 @@ abstract public class ContentProviderNative extends Binder implements IContentPr return super.onTransact(code, data, reply, flags); } + @Override public IBinder asBinder() { return this; @@ -392,14 +382,16 @@ final class ContentProviderProxy implements IContentProvider mRemote = remote; } + @Override public IBinder asBinder() { return mRemote; } - public Cursor query(String callingPkg, Uri url, String[] projection, String selection, - String[] selectionArgs, String sortOrder, ICancellationSignal cancellationSignal) - throws RemoteException { + @Override + public Cursor query(String callingPkg, Uri url, @Nullable String[] projection, + @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) + throws RemoteException { BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor(); Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -416,19 +408,10 @@ final class ContentProviderProxy implements IContentProvider for (int i = 0; i < length; i++) { data.writeString(projection[i]); } - data.writeString(selection); - if (selectionArgs != null) { - length = selectionArgs.length; - } else { - length = 0; - } - data.writeInt(length); - for (int i = 0; i < length; i++) { - data.writeString(selectionArgs[i]); - } - data.writeString(sortOrder); + data.writeBundle(queryArgs); data.writeStrongBinder(adaptor.getObserver().asBinder()); - data.writeStrongBinder(cancellationSignal != null ? cancellationSignal.asBinder() : null); + data.writeStrongBinder( + cancellationSignal != null ? cancellationSignal.asBinder() : null); mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0); @@ -455,6 +438,7 @@ final class ContentProviderProxy implements IContentProvider } } + @Override public String getType(Uri url) throws RemoteException { Parcel data = Parcel.obtain(); @@ -475,6 +459,7 @@ final class ContentProviderProxy implements IContentProvider } } + @Override public Uri insert(String callingPkg, Uri url, ContentValues values) throws RemoteException { Parcel data = Parcel.obtain(); @@ -497,6 +482,7 @@ final class ContentProviderProxy implements IContentProvider } } + @Override public int bulkInsert(String callingPkg, Uri url, ContentValues[] values) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -518,7 +504,8 @@ final class ContentProviderProxy implements IContentProvider } } - public ContentProviderResult[] applyBatch(String callingPkg, + @Override + public ContentProviderResult[] applyBatch(String callingPkg, ArrayList<ContentProviderOperation> operations) throws RemoteException, OperationApplicationException { Parcel data = Parcel.obtain(); @@ -542,6 +529,7 @@ final class ContentProviderProxy implements IContentProvider } } + @Override public int delete(String callingPkg, Uri url, String selection, String[] selectionArgs) throws RemoteException { Parcel data = Parcel.obtain(); @@ -565,6 +553,7 @@ final class ContentProviderProxy implements IContentProvider } } + @Override public int update(String callingPkg, Uri url, ContentValues values, String selection, String[] selectionArgs) throws RemoteException { Parcel data = Parcel.obtain(); @@ -644,6 +633,7 @@ final class ContentProviderProxy implements IContentProvider } } + @Override public Bundle call(String callingPkg, String method, String request, Bundle args) throws RemoteException { Parcel data = Parcel.obtain(); @@ -667,6 +657,7 @@ final class ContentProviderProxy implements IContentProvider } } + @Override public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException { Parcel data = Parcel.obtain(); @@ -715,6 +706,7 @@ final class ContentProviderProxy implements IContentProvider } } + @Override public ICancellationSignal createCancellationSignal() throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -734,6 +726,7 @@ final class ContentProviderProxy implements IContentProvider } } + @Override public Uri canonicalize(String callingPkg, Uri url) throws RemoteException { Parcel data = Parcel.obtain(); @@ -755,6 +748,7 @@ final class ContentProviderProxy implements IContentProvider } } + @Override public Uri uncanonicalize(String callingPkg, Uri url) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -775,6 +769,7 @@ final class ContentProviderProxy implements IContentProvider } } + @Override public boolean refresh(String callingPkg, Uri url, Bundle args, ICancellationSignal signal) throws RemoteException { Parcel data = Parcel.obtain(); diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 54dcd0a2c81c..0fe5ce9da757 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -203,6 +203,26 @@ public abstract class ContentResolver { 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. + */ + public static final String QUERY_ARG_SELECTION = "android:query-selection"; + + /** + * Key for sql selection string arguments list. + * @see #QUERY_ARG_SELECTION + */ + public static final String QUERY_ARG_SELECTION_ARGS = "android:query-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. + */ + public static final String QUERY_ARG_SORT_ORDER = "android:query-sort-order"; + + /** * 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 @@ -517,10 +537,37 @@ 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. + * + * @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) { @@ -539,7 +586,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!!!! @@ -549,8 +596,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; @@ -559,7 +606,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 @@ -2541,6 +2588,7 @@ public abstract class ContentResolver { } try { ISyncStatusObserver.Stub observer = new ISyncStatusObserver.Stub() { + @Override public void onStatusChanged(int which) throws RemoteException { callback.onStatusChanged(which); } @@ -2602,9 +2650,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) { @@ -2615,6 +2662,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) { @@ -2636,8 +2686,8 @@ public abstract class ContentResolver { EventLogTags.CONTENT_QUERY_SAMPLE, uri.toString(), projectionBuffer.toString(), - selection != null ? selection : "", - sortOrder != null ? sortOrder : "", + queryArgs.getString(QUERY_ARG_SELECTION, ""), + queryArgs.getString(QUERY_ARG_SORT_ORDER, ""), durationMillis, blockingPackage != null ? blockingPackage : "", samplePercent); @@ -2751,4 +2801,29 @@ 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_SELECTION, selection); + } + if (selectionArgs != null) { + queryArgs.putStringArray(QUERY_ARG_SELECTION_ARGS, selectionArgs); + } + if (sortOrder != null) { + queryArgs.putString(QUERY_ARG_SORT_ORDER, sortOrder); + } + return queryArgs; + } } diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java index ee8a22fb7f0f..66087fb99cdf 100644 --- a/core/java/android/content/IContentProvider.java +++ b/core/java/android/content/IContentProvider.java @@ -35,9 +35,9 @@ import java.util.ArrayList; * @hide */ public interface IContentProvider extends IInterface { - public Cursor query(String callingPkg, Uri url, String[] projection, String selection, - String[] selectionArgs, String sortOrder, ICancellationSignal cancellationSignal) - throws RemoteException; + public Cursor query(String callingPkg, Uri url, @Nullable String[] projection, + @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) + throws RemoteException; public String getType(Uri url) throws RemoteException; public Uri insert(String callingPkg, Uri url, ContentValues initialValues) throws RemoteException; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 5006433829de..37222ade9886 100755 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -44,6 +44,7 @@ import android.net.ConnectivityManager; import android.net.Uri; import android.net.wifi.WifiManager; import android.os.BatteryManager; +import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.DropBoxManager; import android.os.IBinder; @@ -52,7 +53,6 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; -import android.os.Build.VERSION_CODES; import android.speech.tts.TextToSpeech; import android.text.TextUtils; import android.util.AndroidException; @@ -1580,12 +1580,13 @@ public final class Settings { private final Uri mUri; - private static final String[] SELECT_VALUE = - new String[] { Settings.NameValueTable.VALUE }; + private static final String[] SELECT_VALUE_PROJECTION = new String[] { + Settings.NameValueTable.VALUE + }; private static final String NAME_EQ_PLACEHOLDER = "name=?"; // Must synchronize on 'this' to access mValues and mValuesVersion. - private final HashMap<String, String> mValues = new HashMap<String, String>(); + private final HashMap<String, String> mValues = new HashMap<>(); // Initially null; set lazily and held forever. Synchronized on 'this'. private IContentProvider mContentProvider = null; @@ -1738,8 +1739,9 @@ public final class Settings { Cursor c = null; try { - c = cp.query(cr.getPackageName(), mUri, SELECT_VALUE, NAME_EQ_PLACEHOLDER, - new String[]{name}, null, null); + Bundle queryArgs = ContentResolver.createSqlQueryBundle( + NAME_EQ_PLACEHOLDER, new String[]{name}, null); + c = cp.query(cr.getPackageName(), mUri, SELECT_VALUE_PROJECTION, queryArgs, null); if (c == null) { Log.w(TAG, "Can't get key " + name + " from " + mUri); return null; @@ -1807,7 +1809,7 @@ public final class Settings { private static final HashSet<String> MOVED_TO_SECURE; static { - MOVED_TO_SECURE = new HashSet<String>(30); + MOVED_TO_SECURE = new HashSet<>(30); MOVED_TO_SECURE.add(Secure.ANDROID_ID); MOVED_TO_SECURE.add(Secure.HTTP_PROXY); MOVED_TO_SECURE.add(Secure.LOCATION_PROVIDERS_ALLOWED); @@ -1844,8 +1846,8 @@ public final class Settings { private static final HashSet<String> MOVED_TO_GLOBAL; private static final HashSet<String> MOVED_TO_SECURE_THEN_GLOBAL; static { - MOVED_TO_GLOBAL = new HashSet<String>(); - MOVED_TO_SECURE_THEN_GLOBAL = new HashSet<String>(); + MOVED_TO_GLOBAL = new HashSet<>(); + MOVED_TO_SECURE_THEN_GLOBAL = new HashSet<>(); // these were originally in system but migrated to secure in the past, // so are duplicated in the Secure.* namespace @@ -4163,12 +4165,12 @@ public final class Settings { private static final HashSet<String> MOVED_TO_LOCK_SETTINGS; private static final HashSet<String> MOVED_TO_GLOBAL; static { - MOVED_TO_LOCK_SETTINGS = new HashSet<String>(3); + MOVED_TO_LOCK_SETTINGS = new HashSet<>(3); MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_ENABLED); MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_VISIBLE); MOVED_TO_LOCK_SETTINGS.add(Secure.LOCK_PATTERN_TACTILE_FEEDBACK_ENABLED); - MOVED_TO_GLOBAL = new HashSet<String>(); + MOVED_TO_GLOBAL = new HashSet<>(); MOVED_TO_GLOBAL.add(Settings.Global.ADB_ENABLED); MOVED_TO_GLOBAL.add(Settings.Global.ASSISTED_GPS_ENABLED); MOVED_TO_GLOBAL.add(Settings.Global.BLUETOOTH_ON); @@ -5203,6 +5205,7 @@ public final class Settings { * @hide * @deprecated */ + @Deprecated public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE = "accessibility_display_magnification_auto_update"; @@ -6457,7 +6460,7 @@ public final class Settings { * @hide */ public static final String DOWNLOADS_BACKUP_ENABLED = "downloads_backup_enabled"; - + /** * Whether Downloads folder backup should only occur if the device is using a metered * network. @@ -9139,7 +9142,7 @@ public final class Settings { // Certain settings have been moved from global to the per-user secure namespace private static final HashSet<String> MOVED_TO_SECURE; static { - MOVED_TO_SECURE = new HashSet<String>(1); + MOVED_TO_SECURE = new HashSet<>(1); MOVED_TO_SECURE.add(Settings.Global.INSTALL_NON_MARKET_APPS); } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java index 461573f81789..cbeb8780be2c 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsService.java @@ -243,8 +243,8 @@ final public class SettingsService extends Binder { return lines; } try { - final Cursor cursor = provider.query(resolveCallingPackage(), uri, null, null, null, - null, null); + final Cursor cursor = provider.query(resolveCallingPackage(), uri, null, null, + null); try { while (cursor != null && cursor.moveToNext()) { lines.add(cursor.getString(1) + "=" + cursor.getString(2)); diff --git a/test-runner/src/android/test/mock/MockContentProvider.java b/test-runner/src/android/test/mock/MockContentProvider.java index e443911b2de0..d5f3ce880b8f 100644 --- a/test-runner/src/android/test/mock/MockContentProvider.java +++ b/test-runner/src/android/test/mock/MockContentProvider.java @@ -16,6 +16,7 @@ package android.test.mock; +import android.annotation.Nullable; import android.content.ContentProvider; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; @@ -97,11 +98,11 @@ public class MockContentProvider extends ContentProvider { } @Override - public Cursor query(String callingPackage, Uri url, String[] projection, String selection, - String[] selectionArgs, - String sortOrder, ICancellationSignal cancellationSignal) throws RemoteException { - return MockContentProvider.this.query(url, projection, selection, - selectionArgs, sortOrder); + public Cursor query(String callingPackage, Uri url, @Nullable String[] projection, + @Nullable Bundle queryArgs, + @Nullable ICancellationSignal cancellationSignal) + throws RemoteException { + return MockContentProvider.this.query(url, projection, queryArgs, null); } @Override @@ -248,10 +249,12 @@ public class MockContentProvider extends ContentProvider { throw new UnsupportedOperationException("unimplemented mock method call"); } + @Override public String[] getStreamTypes(Uri url, String mimeTypeFilter) { throw new UnsupportedOperationException("unimplemented mock method call"); } + @Override public AssetFileDescriptor openTypedAssetFile(Uri url, String mimeType, Bundle opts) { throw new UnsupportedOperationException("unimplemented mock method call"); } diff --git a/test-runner/src/android/test/mock/MockIContentProvider.java b/test-runner/src/android/test/mock/MockIContentProvider.java index 09d45d100c2b..112d7eef3dbe 100644 --- a/test-runner/src/android/test/mock/MockIContentProvider.java +++ b/test-runner/src/android/test/mock/MockIContentProvider.java @@ -16,6 +16,7 @@ package android.test.mock; +import android.annotation.Nullable; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentValues; @@ -41,45 +42,52 @@ import java.util.ArrayList; * @hide - @hide because this exposes bulkQuery() and call(), which must also be hidden. */ public class MockIContentProvider implements IContentProvider { + @Override public int bulkInsert(String callingPackage, Uri url, ContentValues[] initialValues) { throw new UnsupportedOperationException("unimplemented mock method"); } + @Override @SuppressWarnings("unused") public int delete(String callingPackage, Uri url, String selection, String[] selectionArgs) throws RemoteException { throw new UnsupportedOperationException("unimplemented mock method"); } + @Override public String getType(Uri url) { throw new UnsupportedOperationException("unimplemented mock method"); } + @Override @SuppressWarnings("unused") public Uri insert(String callingPackage, Uri url, ContentValues initialValues) throws RemoteException { throw new UnsupportedOperationException("unimplemented mock method"); } + @Override public ParcelFileDescriptor openFile( String callingPackage, Uri url, String mode, ICancellationSignal signal, IBinder callerToken) { throw new UnsupportedOperationException("unimplemented mock method"); } + @Override public AssetFileDescriptor openAssetFile( String callingPackage, Uri uri, String mode, ICancellationSignal signal) { throw new UnsupportedOperationException("unimplemented mock method"); } + @Override public ContentProviderResult[] applyBatch(String callingPackage, ArrayList<ContentProviderOperation> operations) { throw new UnsupportedOperationException("unimplemented mock method"); } - public Cursor query(String callingPackage, Uri url, String[] projection, String selection, - String[] selectionArgs, - String sortOrder, ICancellationSignal cancellationSignal) { + @Override + public Cursor query(String callingPackage, Uri url, @Nullable String[] projection, + @Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal) { throw new UnsupportedOperationException("unimplemented mock method"); } @@ -88,24 +96,29 @@ public class MockIContentProvider implements IContentProvider { throw new UnsupportedOperationException("unimplemented mock method"); } + @Override public int update(String callingPackage, Uri url, ContentValues values, String selection, String[] selectionArgs) throws RemoteException { throw new UnsupportedOperationException("unimplemented mock method"); } + @Override public Bundle call(String callingPackage, String method, String request, Bundle args) throws RemoteException { throw new UnsupportedOperationException("unimplemented mock method"); } + @Override public IBinder asBinder() { throw new UnsupportedOperationException("unimplemented mock method"); } + @Override public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException { throw new UnsupportedOperationException("unimplemented mock method"); } + @Override public AssetFileDescriptor openTypedAssetFile(String callingPackage, Uri url, String mimeType, Bundle opts, ICancellationSignal signal) throws RemoteException, FileNotFoundException { throw new UnsupportedOperationException("unimplemented mock method"); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java index 3471165e196b..c827f178279e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java @@ -97,8 +97,8 @@ public final class BridgeContentProvider implements IContentProvider { } @Override - public Cursor query(String callingPackage, Uri arg0, String[] arg1, String arg2, String[] arg3, - String arg4, ICancellationSignal arg5) throws RemoteException { + public Cursor query(String callingPackage, Uri arg0, String[] arg1, + Bundle arg3, ICancellationSignal arg4) throws RemoteException { // TODO Auto-generated method stub return null; } |