diff options
-rw-r--r-- | api/current.txt | 6 | ||||
-rw-r--r-- | core/java/android/content/ContentProvider.java | 78 | ||||
-rw-r--r-- | core/java/android/content/ContentProviderClient.java | 24 | ||||
-rw-r--r-- | core/java/android/content/ContentProviderNative.java | 65 | ||||
-rw-r--r-- | core/java/android/content/ContentResolver.java | 80 | ||||
-rw-r--r-- | core/java/android/content/IContentProvider.java | 5 | ||||
-rw-r--r-- | test-runner/src/android/test/mock/MockContentProvider.java | 10 | ||||
-rw-r--r-- | test-runner/src/android/test/mock/MockIContentProvider.java | 10 |
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"); + } } |