summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt6
-rw-r--r--core/java/android/content/ContentProvider.java78
-rw-r--r--core/java/android/content/ContentProviderClient.java24
-rw-r--r--core/java/android/content/ContentProviderNative.java65
-rw-r--r--core/java/android/content/ContentResolver.java80
-rw-r--r--core/java/android/content/IContentProvider.java5
-rw-r--r--test-runner/src/android/test/mock/MockContentProvider.java10
-rw-r--r--test-runner/src/android/test/mock/MockIContentProvider.java10
8 files changed, 277 insertions, 1 deletions
diff --git a/api/current.txt b/api/current.txt
index 7a00fb9..c7b3ed9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5623,6 +5623,7 @@ package android.content {
method public void attachInfo(android.content.Context, android.content.pm.ProviderInfo);
method public int bulkInsert(android.net.Uri, android.content.ContentValues[]);
method public android.os.Bundle call(java.lang.String, java.lang.String, android.os.Bundle);
+ method public android.net.Uri canonicalize(android.net.Uri);
method public abstract int delete(android.net.Uri, java.lang.String, java.lang.String[]);
method public void dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public final java.lang.String getCallingPackage();
@@ -5652,6 +5653,7 @@ package android.content {
method protected final void setReadPermission(java.lang.String);
method protected final void setWritePermission(java.lang.String);
method public void shutdown();
+ method public android.net.Uri uncanonicalize(android.net.Uri);
method public abstract int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
}
@@ -5663,6 +5665,7 @@ package android.content {
method public android.content.ContentProviderResult[] applyBatch(java.util.ArrayList<android.content.ContentProviderOperation>) throws android.content.OperationApplicationException, android.os.RemoteException;
method public int bulkInsert(android.net.Uri, android.content.ContentValues[]) throws android.os.RemoteException;
method public android.os.Bundle call(java.lang.String, java.lang.String, android.os.Bundle) throws android.os.RemoteException;
+ method public final android.net.Uri canonicalize(android.net.Uri) throws android.os.RemoteException;
method public int delete(android.net.Uri, java.lang.String, java.lang.String[]) throws android.os.RemoteException;
method public android.content.ContentProvider getLocalContentProvider();
method public java.lang.String[] getStreamTypes(android.net.Uri, java.lang.String) throws android.os.RemoteException;
@@ -5677,6 +5680,7 @@ package android.content {
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 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;
}
@@ -5742,6 +5746,7 @@ package android.content {
method public final android.os.Bundle call(android.net.Uri, java.lang.String, java.lang.String, android.os.Bundle);
method public deprecated void cancelSync(android.net.Uri);
method public static void cancelSync(android.accounts.Account, java.lang.String);
+ method public final android.net.Uri canonicalize(android.net.Uri);
method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
method public static deprecated android.content.SyncInfo getCurrentSync();
method public static java.util.List<android.content.SyncInfo> getCurrentSyncs();
@@ -5779,6 +5784,7 @@ package android.content {
method public static void setMasterSyncAutomatically(boolean);
method public static void setSyncAutomatically(android.accounts.Account, java.lang.String, boolean);
method public deprecated void startSync(android.net.Uri, android.os.Bundle);
+ method public final android.net.Uri uncanonicalize(android.net.Uri);
method public final void unregisterContentObserver(android.database.ContentObserver);
method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
method public static void validateSyncExtrasBundle(android.os.Bundle);
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 24c396a..65a3a07 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -348,10 +348,36 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
}
@Override
- public ICancellationSignal createCancellationSignal() throws RemoteException {
+ public ICancellationSignal createCancellationSignal() {
return CancellationSignal.createTransport();
}
+ @Override
+ public Uri canonicalize(String callingPkg, Uri uri) {
+ if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ return null;
+ }
+ mCallingPackage.set(callingPkg);
+ try {
+ return ContentProvider.this.canonicalize(uri);
+ } finally {
+ mCallingPackage.set(null);
+ }
+ }
+
+ @Override
+ public Uri uncanonicalize(String callingPkg, Uri uri) {
+ if (enforceReadPermission(callingPkg, uri) != AppOpsManager.MODE_ALLOWED) {
+ return null;
+ }
+ mCallingPackage.set(callingPkg);
+ try {
+ return ContentProvider.this.uncanonicalize(uri);
+ } finally {
+ mCallingPackage.set(null);
+ }
+ }
+
private void enforceFilePermission(String callingPkg, Uri uri, String mode)
throws FileNotFoundException, SecurityException {
if (mode != null && mode.indexOf('w') != -1) {
@@ -841,6 +867,56 @@ public abstract class ContentProvider implements ComponentCallbacks2 {
public abstract String getType(Uri uri);
/**
+ * Implement this to support canonicalization of URIs that refer to your
+ * content provider. A canonical URI is one that can be transported across
+ * devices, backup/restore, and other contexts, and still be able to refer
+ * to the same data item. Typically this is implemented by adding query
+ * params to the URI allowing the content provider to verify that an incoming
+ * canonical URI references the same data as it was originally intended for and,
+ * if it doesn't, to find that data (if it exists) in the current environment.
+ *
+ * <p>For example, if the content provider holds people and a normal URI in it
+ * is created with a row index into that people database, the cananical representation
+ * may have an additional query param at the end which specifies the name of the
+ * person it is intended for. Later calls into the provider with that URI will look
+ * up the row of that URI's base index and, if it doesn't match or its entry's
+ * name doesn't match the name in the query param, perform a query on its database
+ * to find the correct row to operate on.</p>
+ *
+ * <p>If you implement support for canonical URIs, <b>all</b> incoming calls with
+ * URIs (including this one) must perform this verification and recovery of any
+ * canonical URIs they receive. In addition, you must also implement
+ * {@link #uncanonicalize} to strip the canonicalization of any of these URIs.</p>
+ *
+ * <p>The default implementation of this method returns null, indicating that
+ * canonical URIs are not supported.</p>
+ *
+ * @param url The Uri to canonicalize.
+ *
+ * @return Return the canonical representation of <var>url</var>, or null if
+ * canonicalization of that Uri is not supported.
+ */
+ public Uri canonicalize(Uri url) {
+ return null;
+ }
+
+ /**
+ * Remove canonicalization from canonical URIs previously returned by
+ * {@link #canonicalize}. For example, if your implementation is to add
+ * a query param to canonicalize a URI, this method can simply trip any
+ * query params on the URI. The default implementation always returns the
+ * same <var>url</var> that was passed in.
+ *
+ * @param url The Uri to remove any canonicalization from.
+ *
+ * @return Return the non-canonical representation of <var>url</var>, or return
+ * the <var>url</var> as-is if there is nothing to do. Never return null.
+ */
+ public Uri uncanonicalize(Uri url) {
+ return url;
+ }
+
+ /**
* @hide
* Implementation when a caller has performed an insert on the content
* provider, but that call has been rejected for the operation given
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 4e8dd82..e6d9b24 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -110,6 +110,30 @@ public class ContentProviderClient {
}
}
+ /** See {@link ContentProvider#canonicalize} */
+ public final Uri canonicalize(Uri url) throws RemoteException {
+ try {
+ return mContentProvider.canonicalize(mPackageName, url);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ }
+ }
+
+ /** See {@link ContentProvider#uncanonicalize} */
+ public final Uri uncanonicalize(Uri url) throws RemoteException {
+ try {
+ return mContentProvider.uncanonicalize(mPackageName, url);
+ } catch (DeadObjectException e) {
+ if (!mStable) {
+ mContentResolver.unstableProviderDied(mContentProvider);
+ }
+ throw e;
+ }
+ }
+
/** See {@link ContentProvider#insert ContentProvider.insert} */
public Uri insert(Uri url, ContentValues initialValues)
throws RemoteException {
diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java
index 744e68c..bcf0b63 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -323,6 +323,30 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
reply.writeStrongBinder(cancellationSignal.asBinder());
return true;
}
+
+ case CANONICALIZE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
+ Uri url = Uri.CREATOR.createFromParcel(data);
+
+ Uri out = canonicalize(callingPkg, url);
+ reply.writeNoException();
+ Uri.writeToParcel(reply, out);
+ return true;
+ }
+
+ case UNCANONICALIZE_TRANSACTION:
+ {
+ data.enforceInterface(IContentProvider.descriptor);
+ String callingPkg = data.readString();
+ Uri url = Uri.CREATOR.createFromParcel(data);
+
+ Uri out = uncanonicalize(callingPkg, url);
+ reply.writeNoException();
+ Uri.writeToParcel(reply, out);
+ return true;
+ }
}
} catch (Exception e) {
DatabaseUtils.writeExceptionToParcel(reply, e);
@@ -685,5 +709,46 @@ final class ContentProviderProxy implements IContentProvider
}
}
+ public Uri canonicalize(String callingPkg, Uri url) throws RemoteException
+ {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ url.writeToParcel(data, 0);
+
+ mRemote.transact(IContentProvider.CANONICALIZE_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ Uri out = Uri.CREATOR.createFromParcel(reply);
+ return out;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
+ public Uri uncanonicalize(String callingPkg, Uri url) throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ try {
+ data.writeInterfaceToken(IContentProvider.descriptor);
+
+ data.writeString(callingPkg);
+ url.writeToParcel(data, 0);
+
+ mRemote.transact(IContentProvider.UNCANONICALIZE_TRANSACTION, data, reply, 0);
+
+ DatabaseUtils.readExceptionFromParcel(reply);
+ Uri out = Uri.CREATOR.createFromParcel(reply);
+ return out;
+ } finally {
+ data.recycle();
+ reply.recycle();
+ }
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 8a5a56c..9f462aa 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -497,6 +497,86 @@ public abstract class ContentResolver {
}
/**
+ * Transform the given <var>url</var> to a canonical representation of
+ * its referenced resource, which can be used across devices, persisted,
+ * backed up and restored, etc. The returned Uri is still a fully capable
+ * Uri for use with its content provider, allowing you to do all of the
+ * same content provider operations as with the original Uri --
+ * {@link #query}, {@link #openInputStream(android.net.Uri)}, etc. The
+ * only difference in behavior between the original and new Uris is that
+ * the content provider may need to do some additional work at each call
+ * using it to resolve it to the correct resource, especially if the
+ * canonical Uri has been moved to a different environment.
+ *
+ * <p>If you are moving a canonical Uri between environments, you should
+ * perform another call to {@link #canonicalize} with that original Uri to
+ * re-canonicalize it for the current environment. Alternatively, you may
+ * want to use {@link #uncanonicalize} to transform it to a non-canonical
+ * Uri that works only in the current environment but potentially more
+ * efficiently than the canonical representation.</p>
+ *
+ * @param url The {@link Uri} that is to be transformed to a canonical
+ * representation. Like all resolver calls, the input can be either
+ * a non-canonical or canonical Uri.
+ *
+ * @return Returns the official canonical representation of <var>url</var>,
+ * or null if the content provider does not support a canonical representation
+ * of the given Uri. Many providers may not support canonicalization of some
+ * or all of their Uris.
+ *
+ * @see #uncanonicalize
+ */
+ public final Uri canonicalize(Uri url) {
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ return null;
+ }
+
+ try {
+ return provider.canonicalize(mPackageName, url);
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return null;
+ } finally {
+ releaseProvider(provider);
+ }
+ }
+
+ /**
+ * Given a canonical Uri previously generated by {@link #canonicalize}, convert
+ * it to its local non-canonical form. This can be useful in some cases where
+ * you know that you will only be using the Uri in the current environment and
+ * want to avoid any possible overhead when using it with the content
+ * provider.
+ *
+ * @param url The canonical {@link Uri} that is to be convered back to its
+ * non-canonical form.
+ *
+ * @return Returns the non-canonical representation of <var>url</var>. This
+ * function never returns null; if there is no conversion to be done, it returns
+ * the same Uri that was provided.
+ *
+ * @see #canonicalize
+ */
+ public final Uri uncanonicalize(Uri url) {
+ IContentProvider provider = acquireProvider(url);
+ if (provider == null) {
+ return null;
+ }
+
+ try {
+ return provider.uncanonicalize(mPackageName, url);
+ } catch (RemoteException e) {
+ // Arbitrary and not worth documenting, as Activity
+ // Manager will kill this process shortly anyway.
+ return null;
+ } 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.
*
diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java
index 6ea8876..f92a404 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -59,6 +59,9 @@ public interface IContentProvider extends IInterface {
throws RemoteException;
public ICancellationSignal createCancellationSignal() throws RemoteException;
+ public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException;
+ public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException;
+
// Data interchange.
public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException;
public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType,
@@ -80,4 +83,6 @@ public interface IContentProvider extends IInterface {
static final int GET_STREAM_TYPES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 21;
static final int OPEN_TYPED_ASSET_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 22;
static final int CREATE_CANCELATION_SIGNAL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 23;
+ static final int CANONICALIZE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 24;
+ static final int UNCANONICALIZE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 25;
}
diff --git a/test-runner/src/android/test/mock/MockContentProvider.java b/test-runner/src/android/test/mock/MockContentProvider.java
index 596ea0a..28d52b0 100644
--- a/test-runner/src/android/test/mock/MockContentProvider.java
+++ b/test-runner/src/android/test/mock/MockContentProvider.java
@@ -137,6 +137,16 @@ public class MockContentProvider extends ContentProvider {
public ICancellationSignal createCancellationSignal() throws RemoteException {
return null;
}
+
+ @Override
+ public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException {
+ return MockContentProvider.this.canonicalize(uri);
+ }
+
+ @Override
+ public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException {
+ return MockContentProvider.this.uncanonicalize(uri);
+ }
}
private final InversionIContentProvider mIContentProvider = new InversionIContentProvider();
diff --git a/test-runner/src/android/test/mock/MockIContentProvider.java b/test-runner/src/android/test/mock/MockIContentProvider.java
index b14ce49..c0dc7c3 100644
--- a/test-runner/src/android/test/mock/MockIContentProvider.java
+++ b/test-runner/src/android/test/mock/MockIContentProvider.java
@@ -114,4 +114,14 @@ public class MockIContentProvider implements IContentProvider {
public ICancellationSignal createCancellationSignal() throws RemoteException {
throw new UnsupportedOperationException("unimplemented mock method");
}
+
+ @Override
+ public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
+
+ @Override
+ public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException {
+ throw new UnsupportedOperationException("unimplemented mock method");
+ }
}