diff options
8 files changed, 193 insertions, 0 deletions
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 1e6424e165c5..49b58536b3ed 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -42,6 +42,7 @@ import android.os.ICancellationSignal; import android.os.OperationCanceledException; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.os.RemoteException; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; @@ -463,6 +464,23 @@ public abstract class ContentProvider implements ComponentCallbacks2 { } } + @Override + public boolean refresh(String callingPkg, Uri uri, Bundle args, + ICancellationSignal cancellationSignal) throws RemoteException { + validateIncomingUri(uri); + uri = getUriWithoutUserId(uri); + if (enforceReadPermission(callingPkg, uri, null) != AppOpsManager.MODE_ALLOWED) { + return false; + } + final String original = setCallingPackage(callingPkg); + try { + return ContentProvider.this.refresh(uri, args, + CancellationSignal.fromTransport(cancellationSignal)); + } finally { + setCallingPackage(original); + } + } + private void enforceFilePermission(String callingPkg, Uri uri, String mode, IBinder callerToken) throws FileNotFoundException, SecurityException { if (mode != null && mode.indexOf('w') != -1) { @@ -1093,6 +1111,34 @@ public abstract class ContentProvider implements ComponentCallbacks2 { } /** + * Implement this to support refresh of content identified by {@code uri}. By default, this + * method returns false; providers who wish to implement this should return true to signal the + * client that the provider has tried refreshing with its own implementation. + * <p> + * This allows clients to request an explicit refresh of content identified by {@code uri}. + * <p> + * Client code should only invoke this method when there is a strong indication (such as a user + * initiated pull to refresh gesture) that the content is stale. + * <p> + * Remember to send {@link ContentResolver#notifyChange(Uri, android.database.ContentObserver)} + * notifications when content changes. + * + * @param uri The Uri identifying the data to refresh. + * @param args Additional options from the client. The definitions of these are specific to the + * content provider being called. + * @param cancellationSignal A signal to cancel the operation in progress, or {@code null} if + * none. For example, if you called refresh on a particular uri, you should call + * {@link CancellationSignal#throwIfCanceled()} to check whether the client has + * canceled the refresh request. + * @return true if the provider actually tried refreshing. + * @hide + */ + public boolean refresh(Uri uri, @Nullable Bundle args, + @Nullable CancellationSignal cancellationSignal) { + return false; + } + + /** * @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 9221fbb50c96..857610d08923 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -234,6 +234,30 @@ public class ContentProviderClient implements AutoCloseable { } } + /** @hide */ + public boolean refresh(Uri url, @Nullable Bundle args, + @Nullable CancellationSignal cancellationSignal) throws RemoteException { + Preconditions.checkNotNull(url, "url"); + + beforeRemote(); + try { + ICancellationSignal remoteCancellationSignal = null; + if (cancellationSignal != null) { + cancellationSignal.throwIfCanceled(); + remoteCancellationSignal = mContentProvider.createCancellationSignal(); + cancellationSignal.setRemote(remoteCancellationSignal); + } + return mContentProvider.refresh(mPackageName, url, args, remoteCancellationSignal); + } catch (DeadObjectException e) { + if (!mStable) { + mContentResolver.unstableProviderDied(mContentProvider); + } + throw e; + } finally { + afterRemote(); + } + } + /** See {@link ContentProvider#insert ContentProvider.insert} */ public @Nullable Uri insert(@NonNull Uri url, @Nullable ContentValues initialValues) throws RemoteException { diff --git a/core/java/android/content/ContentProviderNative.java b/core/java/android/content/ContentProviderNative.java index 439e1ff48c17..eadc013db16e 100644 --- a/core/java/android/content/ContentProviderNative.java +++ b/core/java/android/content/ContentProviderNative.java @@ -355,6 +355,20 @@ abstract public class ContentProviderNative extends Binder implements IContentPr Uri.writeToParcel(reply, out); return true; } + + case REFRESH_TRANSACTION: { + data.enforceInterface(IContentProvider.descriptor); + String callingPkg = data.readString(); + Uri url = Uri.CREATOR.createFromParcel(data); + Bundle args = data.readBundle(); + ICancellationSignal signal = ICancellationSignal.Stub.asInterface( + data.readStrongBinder()); + + boolean out = refresh(callingPkg, url, args, signal); + reply.writeNoException(); + reply.writeInt(out ? 0 : -1); + return true; + } } } catch (Exception e) { DatabaseUtils.writeExceptionToParcel(reply, e); @@ -761,5 +775,28 @@ final class ContentProviderProxy implements IContentProvider } } + public boolean refresh(String callingPkg, Uri url, Bundle args, ICancellationSignal signal) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + try { + data.writeInterfaceToken(IContentProvider.descriptor); + + data.writeString(callingPkg); + url.writeToParcel(data, 0); + data.writeBundle(args); + data.writeStrongBinder(signal != null ? signal.asBinder() : null); + + mRemote.transact(IContentProvider.REFRESH_TRANSACTION, data, reply, 0); + + DatabaseUtils.readExceptionFromParcel(reply); + int success = reply.readInt(); + return (success == 0); + } 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 daa1b93889cc..705c091a6423 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -194,6 +194,15 @@ public abstract class ContentResolver { public static final String EXTRA_SIZE = "android.content.extra.SIZE"; /** + * An extra boolean describing whether a particular provider supports refresh + * or not. If a provider supports refresh, it should include this key in its + * returned Cursor as part of its query call. + * + * @hide + */ + public static final String EXTRA_REFRESH_SUPPORTED = "android.content.extra.REFRESH_SUPPORTED"; + + /** * 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 @@ -664,6 +673,54 @@ public abstract class ContentResolver { } /** + * Implement this to support refresh of content identified by {@code uri}. By default, this + * method returns false; providers who wish to implement this should return true to signal the + * client that the provider has tried refreshing with its own implementation. + * <p> + * This allows clients to request an explicit refresh of content identified by {@code uri}. + * <p> + * Client code should only invoke this method when there is a strong indication (such as a user + * initiated pull to refresh gesture) that the content is stale. + * <p> + * Remember to send {@link ContentResolver#notifyChange(Uri, android.database.ContentObserver)} + * notifications when content changes. + * + * @param uri The Uri identifying the data to refresh. + * @param args Additional options from the client. The definitions of these are specific to the + * content provider being called. + * @param cancellationSignal A signal to cancel the operation in progress, or {@code null} if + * none. For example, if you called refresh on a particular uri, you should call + * {@link CancellationSignal#throwIfCanceled()} to check whether the client has + * canceled the refresh request. + * @return true if the provider actually tried refreshing. + * @hide + */ + public final boolean refresh(@NonNull Uri url, @Nullable Bundle args, + @Nullable CancellationSignal cancellationSignal) { + Preconditions.checkNotNull(url, "url"); + IContentProvider provider = acquireProvider(url); + if (provider == null) { + return false; + } + + try { + ICancellationSignal remoteCancellationSignal = null; + if (cancellationSignal != null) { + cancellationSignal.throwIfCanceled(); + remoteCancellationSignal = provider.createCancellationSignal(); + cancellationSignal.setRemote(remoteCancellationSignal); + } + return provider.refresh(mPackageName, url, args, remoteCancellationSignal); + } catch (RemoteException e) { + // Arbitrary and not worth documenting, as Activity + // Manager will kill this process shortly anyway. + return false; + } finally { + releaseProvider(provider); + } + } + + /** * Open a stream on to the content associated with a content URI. If there * is no data associated with the URI, FileNotFoundException is thrown. * diff --git a/core/java/android/content/IContentProvider.java b/core/java/android/content/IContentProvider.java index 4afe38b62851..ee8a22fb7f0f 100644 --- a/core/java/android/content/IContentProvider.java +++ b/core/java/android/content/IContentProvider.java @@ -65,6 +65,9 @@ public interface IContentProvider extends IInterface { public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException; public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException; + public boolean refresh(String callingPkg, Uri url, @Nullable Bundle args, + ICancellationSignal cancellationSignal) throws RemoteException; + // Data interchange. public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException; public AssetFileDescriptor openTypedAssetFile(String callingPkg, Uri url, String mimeType, @@ -88,4 +91,5 @@ public interface IContentProvider extends IInterface { 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; + static final int REFRESH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 26; } diff --git a/test-runner/src/android/test/mock/MockContentProvider.java b/test-runner/src/android/test/mock/MockContentProvider.java index 5ef71df341bf..e443911b2de0 100644 --- a/test-runner/src/android/test/mock/MockContentProvider.java +++ b/test-runner/src/android/test/mock/MockContentProvider.java @@ -147,6 +147,12 @@ public class MockContentProvider extends ContentProvider { public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException { return MockContentProvider.this.uncanonicalize(uri); } + + @Override + public boolean refresh(String callingPkg, Uri url, Bundle args, + ICancellationSignal cancellationSignal) throws RemoteException { + return MockContentProvider.this.refresh(url, args); + } } private final InversionIContentProvider mIContentProvider = new InversionIContentProvider(); @@ -251,6 +257,13 @@ public class MockContentProvider extends ContentProvider { } /** + * @hide + */ + public boolean refresh(Uri url, Bundle args) { + throw new UnsupportedOperationException("unimplemented mock method call"); + } + + /** * Returns IContentProvider which calls back same methods in this class. * By overriding this class, we avoid the mechanism hidden behind ContentProvider * (IPC, etc.) diff --git a/test-runner/src/android/test/mock/MockIContentProvider.java b/test-runner/src/android/test/mock/MockIContentProvider.java index ee8c376b0110..09d45d100c2b 100644 --- a/test-runner/src/android/test/mock/MockIContentProvider.java +++ b/test-runner/src/android/test/mock/MockIContentProvider.java @@ -125,4 +125,10 @@ public class MockIContentProvider implements IContentProvider { public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException { throw new UnsupportedOperationException("unimplemented mock method"); } + + @Override + public boolean refresh(String callingPkg, Uri url, Bundle args, + ICancellationSignal cancellationSignal) throws RemoteException { + 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 e4cbb2f4c02d..3471165e196b 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 @@ -145,4 +145,10 @@ public final class BridgeContentProvider implements IContentProvider { public Uri uncanonicalize(String callingPkg, Uri uri) throws RemoteException { return null; } + + @Override + public boolean refresh(String callingPkg, Uri url, Bundle args, + ICancellationSignal cancellationSignal) throws RemoteException { + return false; + } } |