Replace ContentProvider SQL args w/ Bundle & Constants.
Test: cts-tradefed run cts-dev -m CtsContentTestCases
Bug: 30927484
Change-Id: Idb9dbc2b80896e9f8474a0db71353b7a3810d597
diff --git a/api/current.txt b/api/current.txt
index 5a2df57..ce3c67d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -7978,6 +7978,7 @@
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 @@
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 @@
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 @@
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 7a287df..2316ebe 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -8313,6 +8313,7 @@
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 @@
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 @@
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 @@
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 20b723d..6a79309 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -8000,6 +8000,7 @@
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 @@
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 @@
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 @@
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 63641a8..3687f10 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 @@
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 @@
@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 32b4595..b0ab235 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.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 @@
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 f369409..cda98e5 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -97,6 +97,7 @@
* developer guide.</p>
*/
public abstract class ContentProvider implements ComponentCallbacks2 {
+
private static final String TAG = "ContentProvider";
/*
@@ -118,7 +119,7 @@
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 @@
}
@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 @@
// 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 @@
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 @@
* (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 @@
* <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 @@
}
/**
+ * 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 @@
* 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 @@
/**
* 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 fd6cddb..732666f 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -128,11 +128,20 @@
}
/** 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 @@
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 eadc013..d428a3a 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 @@
}
}
- // 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 @@
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 @@
return super.onTransact(code, data, reply, flags);
}
+ @Override
public IBinder asBinder()
{
return this;
@@ -392,14 +382,16 @@
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 @@
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 @@
}
}
+ @Override
public String getType(Uri url) throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -475,6 +459,7 @@
}
}
+ @Override
public Uri insert(String callingPkg, Uri url, ContentValues values) throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -497,6 +482,7 @@
}
}
+ @Override
public int bulkInsert(String callingPkg, Uri url, ContentValues[] values) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -518,7 +504,8 @@
}
}
- 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 @@
}
}
+ @Override
public int delete(String callingPkg, Uri url, String selection, String[] selectionArgs)
throws RemoteException {
Parcel data = Parcel.obtain();
@@ -565,6 +553,7 @@
}
}
+ @Override
public int update(String callingPkg, Uri url, ContentValues values, String selection,
String[] selectionArgs) throws RemoteException {
Parcel data = Parcel.obtain();
@@ -644,6 +633,7 @@
}
}
+ @Override
public Bundle call(String callingPkg, String method, String request, Bundle args)
throws RemoteException {
Parcel data = Parcel.obtain();
@@ -667,6 +657,7 @@
}
}
+ @Override
public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -715,6 +706,7 @@
}
}
+ @Override
public ICancellationSignal createCancellationSignal() throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -734,6 +726,7 @@
}
}
+ @Override
public Uri canonicalize(String callingPkg, Uri url) throws RemoteException
{
Parcel data = Parcel.obtain();
@@ -755,6 +748,7 @@
}
}
+ @Override
public Uri uncanonicalize(String callingPkg, Uri url) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -775,6 +769,7 @@
}
}
+ @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 54dcd0a..0fe5ce9 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -203,6 +203,26 @@
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 @@
* @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 @@
}
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 @@
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 @@
// 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 @@
}
try {
ISyncStatusObserver.Stub observer = new ISyncStatusObserver.Stub() {
+ @Override
public void onStatusChanged(int which) throws RemoteException {
callback.onStatusChanged(which);
}
@@ -2602,9 +2650,8 @@
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 @@
}
}
+ // 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 @@
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 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 ee8a22f..66087fb 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -35,9 +35,9 @@
* @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 5006433..37222ad 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -44,6 +44,7 @@
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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
* @hide
* @deprecated
*/
+ @Deprecated
public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE =
"accessibility_display_magnification_auto_update";
@@ -6457,7 +6460,7 @@
* @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 @@
// 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 461573f..cbeb878 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 @@
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 e443911..d5f3ce8 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 @@
}
@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 @@
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 09d45d1..112d7ee 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 @@
* @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 @@
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 3471165..c827f17 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 @@
}
@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;
}