Adding ContentProvider#refresh and ContentResolver#refresh.
Original CL is from ag/1568530.
Bug: 31647485
Change-Id: Ib45fc995a361b8c75cd3600f638910b18a263d51
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 1e6424e..49b5853 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -42,6 +42,7 @@
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 @@
}
}
+ @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 @@
}
/**
+ * 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 9221fbb..857610d 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -234,6 +234,30 @@
}
}
+ /** @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 439e1ff..eadc013 100644
--- a/core/java/android/content/ContentProviderNative.java
+++ b/core/java/android/content/ContentProviderNative.java
@@ -355,6 +355,20 @@
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 @@
}
}
+ 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 daa1b93..705c091 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -194,6 +194,15 @@
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 @@
}
/**
+ * 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 4afe38b..ee8a22f 100644
--- a/core/java/android/content/IContentProvider.java
+++ b/core/java/android/content/IContentProvider.java
@@ -65,6 +65,9 @@
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 @@
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 5ef71df..e443911 100644
--- a/test-runner/src/android/test/mock/MockContentProvider.java
+++ b/test-runner/src/android/test/mock/MockContentProvider.java
@@ -147,6 +147,12 @@
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 @@
}
/**
+ * @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 ee8c376..09d45d1 100644
--- a/test-runner/src/android/test/mock/MockIContentProvider.java
+++ b/test-runner/src/android/test/mock/MockIContentProvider.java
@@ -125,4 +125,10 @@
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 e4cbb2f..3471165 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 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;
+ }
}