Merge "Add async version of "canonicalize""
This commit is contained in:
committed by
Android (Google) Code Review
commit
23132a29eb
@@ -579,6 +579,15 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void canonicalizeAsync(String callingPkg, @Nullable String featureId, Uri uri,
|
||||
RemoteCallback callback) {
|
||||
final Bundle result = new Bundle();
|
||||
result.putParcelable(ContentResolver.REMOTE_CALLBACK_RESULT,
|
||||
canonicalize(callingPkg, featureId, uri));
|
||||
callback.sendResult(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri uncanonicalize(String callingPkg, String featureId, Uri uri) {
|
||||
uri = validateIncomingUri(uri);
|
||||
|
||||
@@ -359,6 +359,16 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
|
||||
return true;
|
||||
}
|
||||
|
||||
case CANONICALIZE_ASYNC_TRANSACTION: {
|
||||
data.enforceInterface(IContentProvider.descriptor);
|
||||
String callingPkg = data.readString();
|
||||
String featureId = data.readString();
|
||||
Uri uri = Uri.CREATOR.createFromParcel(data);
|
||||
RemoteCallback callback = RemoteCallback.CREATOR.createFromParcel(data);
|
||||
canonicalizeAsync(callingPkg, featureId, uri, callback);
|
||||
return true;
|
||||
}
|
||||
|
||||
case UNCANONICALIZE_TRANSACTION:
|
||||
{
|
||||
data.enforceInterface(IContentProvider.descriptor);
|
||||
@@ -822,6 +832,25 @@ final class ContentProviderProxy implements IContentProvider
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
/* oneway */ public void canonicalizeAsync(String callingPkg, @Nullable String featureId,
|
||||
Uri uri, RemoteCallback callback) throws RemoteException {
|
||||
Parcel data = Parcel.obtain();
|
||||
try {
|
||||
data.writeInterfaceToken(IContentProvider.descriptor);
|
||||
|
||||
data.writeString(callingPkg);
|
||||
data.writeString(featureId);
|
||||
uri.writeToParcel(data, 0);
|
||||
callback.writeToParcel(data, 0);
|
||||
|
||||
mRemote.transact(IContentProvider.CANONICALIZE_ASYNC_TRANSACTION, data, null,
|
||||
Binder.FLAG_ONEWAY);
|
||||
} finally {
|
||||
data.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri uncanonicalize(String callingPkg, @Nullable String featureId, Uri url)
|
||||
throws RemoteException {
|
||||
|
||||
@@ -712,14 +712,17 @@ public abstract class ContentResolver implements ContentInterface {
|
||||
* {@link #CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS}.
|
||||
* @hide
|
||||
*/
|
||||
public static final int CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS =
|
||||
public static final int CONTENT_PROVIDER_READY_TIMEOUT_MILLIS =
|
||||
CONTENT_PROVIDER_PUBLISH_TIMEOUT_MILLIS + 10 * 1000;
|
||||
|
||||
// Timeout given a ContentProvider that has already been started and connected to.
|
||||
private static final int CONTENT_PROVIDER_TIMEOUT_MILLIS = 3 * 1000;
|
||||
|
||||
// Should be >= {@link #CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS}, because that's how
|
||||
// long ActivityManagerService is giving a content provider to get published if a new process
|
||||
// needs to be started for that.
|
||||
private static final int GET_TYPE_TIMEOUT_MILLIS =
|
||||
CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS + 5 * 1000;
|
||||
private static final int REMOTE_CONTENT_PROVIDER_TIMEOUT_MILLIS =
|
||||
CONTENT_PROVIDER_READY_TIMEOUT_MILLIS + CONTENT_PROVIDER_TIMEOUT_MILLIS;
|
||||
|
||||
public ContentResolver(@Nullable Context context) {
|
||||
this(context, null);
|
||||
@@ -833,10 +836,10 @@ public abstract class ContentResolver implements ContentInterface {
|
||||
IContentProvider provider = acquireExistingProvider(url);
|
||||
if (provider != null) {
|
||||
try {
|
||||
final GetTypeResultListener resultListener = new GetTypeResultListener();
|
||||
final StringResultListener resultListener = new StringResultListener();
|
||||
provider.getTypeAsync(url, new RemoteCallback(resultListener));
|
||||
resultListener.waitForResult();
|
||||
return resultListener.type;
|
||||
resultListener.waitForResult(CONTENT_PROVIDER_TIMEOUT_MILLIS);
|
||||
return resultListener.result;
|
||||
} catch (RemoteException e) {
|
||||
// Arbitrary and not worth documenting, as Activity
|
||||
// Manager will kill this process shortly anyway.
|
||||
@@ -854,13 +857,13 @@ public abstract class ContentResolver implements ContentInterface {
|
||||
}
|
||||
|
||||
try {
|
||||
GetTypeResultListener resultListener = new GetTypeResultListener();
|
||||
final StringResultListener resultListener = new StringResultListener();
|
||||
ActivityManager.getService().getProviderMimeTypeAsync(
|
||||
ContentProvider.getUriWithoutUserId(url),
|
||||
resolveUserId(url),
|
||||
new RemoteCallback(resultListener));
|
||||
resultListener.waitForResult();
|
||||
return resultListener.type;
|
||||
resultListener.waitForResult(REMOTE_CONTENT_PROVIDER_TIMEOUT_MILLIS);
|
||||
return resultListener.result;
|
||||
} catch (RemoteException e) {
|
||||
// We just failed to send a oneway request to the System Server. Nothing to do.
|
||||
return null;
|
||||
@@ -870,27 +873,29 @@ public abstract class ContentResolver implements ContentInterface {
|
||||
}
|
||||
}
|
||||
|
||||
private static class GetTypeResultListener implements RemoteCallback.OnResultListener {
|
||||
private abstract static class ResultListener<T> implements RemoteCallback.OnResultListener {
|
||||
@GuardedBy("this")
|
||||
public boolean done;
|
||||
|
||||
@GuardedBy("this")
|
||||
public String type;
|
||||
public T result;
|
||||
|
||||
@Override
|
||||
public void onResult(Bundle result) {
|
||||
synchronized (this) {
|
||||
type = result.getString(REMOTE_CALLBACK_RESULT);
|
||||
this.result = getResultFromBundle(result);
|
||||
done = true;
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void waitForResult() {
|
||||
protected abstract T getResultFromBundle(Bundle result);
|
||||
|
||||
public void waitForResult(long timeout) {
|
||||
synchronized (this) {
|
||||
if (!done) {
|
||||
try {
|
||||
wait(GET_TYPE_TIMEOUT_MILLIS);
|
||||
wait(timeout);
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore
|
||||
}
|
||||
@@ -899,6 +904,20 @@ public abstract class ContentResolver implements ContentInterface {
|
||||
}
|
||||
}
|
||||
|
||||
private static class StringResultListener extends ResultListener<String> {
|
||||
@Override
|
||||
protected String getResultFromBundle(Bundle result) {
|
||||
return result.getString(REMOTE_CALLBACK_RESULT);
|
||||
}
|
||||
}
|
||||
|
||||
private static class UriResultListener extends ResultListener<Uri> {
|
||||
@Override
|
||||
protected Uri getResultFromBundle(Bundle result) {
|
||||
return result.getParcelable(REMOTE_CALLBACK_RESULT);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query for the possible MIME types for the representations the given
|
||||
* content URL can be returned when opened as as stream with
|
||||
@@ -1192,7 +1211,11 @@ public abstract class ContentResolver implements ContentInterface {
|
||||
}
|
||||
|
||||
try {
|
||||
return provider.canonicalize(mPackageName, mFeatureId, url);
|
||||
final UriResultListener resultListener = new UriResultListener();
|
||||
provider.canonicalizeAsync(mPackageName, mFeatureId, url,
|
||||
new RemoteCallback(resultListener));
|
||||
resultListener.waitForResult(CONTENT_PROVIDER_TIMEOUT_MILLIS);
|
||||
return resultListener.result;
|
||||
} catch (RemoteException e) {
|
||||
// Arbitrary and not worth documenting, as Activity
|
||||
// Manager will kill this process shortly anyway.
|
||||
|
||||
@@ -45,7 +45,7 @@ public interface IContentProvider extends IInterface {
|
||||
public String getType(Uri url) throws RemoteException;
|
||||
|
||||
/**
|
||||
* An oneway version of getType. The functionality is exactly the same, except that the
|
||||
* A oneway version of getType. The functionality is exactly the same, except that the
|
||||
* call returns immediately, and the resulting type is returned when available via
|
||||
* a binder callback.
|
||||
*/
|
||||
@@ -126,6 +126,14 @@ public interface IContentProvider extends IInterface {
|
||||
public Uri canonicalize(String callingPkg, @Nullable String featureId, Uri uri)
|
||||
throws RemoteException;
|
||||
|
||||
/**
|
||||
* A oneway version of canonicalize. The functionality is exactly the same, except that the
|
||||
* call returns immediately, and the resulting type is returned when available via
|
||||
* a binder callback.
|
||||
*/
|
||||
void canonicalizeAsync(String callingPkg, @Nullable String featureId, Uri uri,
|
||||
RemoteCallback callback) throws RemoteException;
|
||||
|
||||
public Uri uncanonicalize(String callingPkg, @Nullable String featureId, Uri uri)
|
||||
throws RemoteException;
|
||||
|
||||
@@ -162,4 +170,5 @@ public interface IContentProvider extends IInterface {
|
||||
static final int REFRESH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 26;
|
||||
static final int CHECK_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 27;
|
||||
int GET_TYPE_ASYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 28;
|
||||
int CANONICALIZE_ASYNC_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 29;
|
||||
}
|
||||
|
||||
@@ -234,4 +234,12 @@ public class ContentResolverTest {
|
||||
assertThat(type).isNull();
|
||||
assertThat(end).isLessThan(start + 5000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanonicalize() {
|
||||
Uri canonical = mResolver.canonicalize(
|
||||
Uri.parse("content://android.content.FakeProviderRemote/something"));
|
||||
assertThat(canonical).isEqualTo(
|
||||
Uri.parse("content://android.content.FakeProviderRemote/canonical"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,4 +54,10 @@ public class FakeProviderRemote extends ContentProvider {
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri canonicalize(Uri uri) {
|
||||
return new Uri.Builder().scheme(uri.getScheme()).authority(uri.getAuthority())
|
||||
.appendPath("canonical").build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7271,7 +7271,7 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
|
||||
// Wait for the provider to be published...
|
||||
final long timeout =
|
||||
SystemClock.uptimeMillis() + ContentResolver.CONTENT_PROVIDER_WAIT_TIMEOUT_MILLIS;
|
||||
SystemClock.uptimeMillis() + ContentResolver.CONTENT_PROVIDER_READY_TIMEOUT_MILLIS;
|
||||
boolean timedOut = false;
|
||||
synchronized (cpr) {
|
||||
while (cpr.provider == null) {
|
||||
|
||||
@@ -29,6 +29,7 @@ android_test {
|
||||
libs: [
|
||||
"android.test.runner",
|
||||
"android.test.base",
|
||||
"android.test.mock",
|
||||
],
|
||||
|
||||
dxflags: ["--multi-dex"],
|
||||
|
||||
@@ -36,9 +36,10 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.reset;
|
||||
@@ -67,6 +68,7 @@ import android.provider.Settings;
|
||||
import android.provider.Settings.Global;
|
||||
import android.provider.Settings.Secure;
|
||||
import android.service.notification.ConversationChannelWrapper;
|
||||
import android.test.mock.MockIContentProvider;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.testing.TestableContentResolver;
|
||||
import android.util.ArrayMap;
|
||||
@@ -87,6 +89,7 @@ import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.Spy;
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
@@ -123,7 +126,7 @@ public class PreferencesHelperTest extends UiServiceTestCase {
|
||||
@Mock NotificationUsageStats mUsageStats;
|
||||
@Mock RankingHandler mHandler;
|
||||
@Mock PackageManager mPm;
|
||||
@Mock IContentProvider mTestIContentProvider;
|
||||
@Spy IContentProvider mTestIContentProvider = new MockIContentProvider();
|
||||
@Mock Context mContext;
|
||||
@Mock ZenModeHelper mMockZenModeHelper;
|
||||
|
||||
@@ -170,12 +173,12 @@ public class PreferencesHelperTest extends UiServiceTestCase {
|
||||
when(testContentProvider.getIContentProvider()).thenReturn(mTestIContentProvider);
|
||||
contentResolver.addProvider(TEST_AUTHORITY, testContentProvider);
|
||||
|
||||
when(mTestIContentProvider.canonicalize(any(), any(), eq(SOUND_URI)))
|
||||
.thenReturn(CANONICAL_SOUND_URI);
|
||||
when(mTestIContentProvider.canonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
|
||||
.thenReturn(CANONICAL_SOUND_URI);
|
||||
when(mTestIContentProvider.uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
|
||||
.thenReturn(SOUND_URI);
|
||||
doReturn(CANONICAL_SOUND_URI)
|
||||
.when(mTestIContentProvider).canonicalize(any(), any(), eq(SOUND_URI));
|
||||
doReturn(CANONICAL_SOUND_URI)
|
||||
.when(mTestIContentProvider).canonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
|
||||
doReturn(SOUND_URI)
|
||||
.when(mTestIContentProvider).uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
|
||||
|
||||
mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0,
|
||||
NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0);
|
||||
@@ -506,12 +509,13 @@ public class PreferencesHelperTest extends UiServiceTestCase {
|
||||
.appendQueryParameter("title", "Test")
|
||||
.appendQueryParameter("canonical", "1")
|
||||
.build();
|
||||
when(mTestIContentProvider.canonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
|
||||
.thenReturn(canonicalBasedOnLocal);
|
||||
when(mTestIContentProvider.uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
|
||||
.thenReturn(localUri);
|
||||
when(mTestIContentProvider.uncanonicalize(any(), any(), eq(canonicalBasedOnLocal)))
|
||||
.thenReturn(localUri);
|
||||
doReturn(canonicalBasedOnLocal)
|
||||
.when(mTestIContentProvider).canonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
|
||||
doReturn(localUri)
|
||||
.when(mTestIContentProvider).uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
|
||||
doReturn(localUri)
|
||||
.when(mTestIContentProvider).uncanonicalize(any(), any(),
|
||||
eq(canonicalBasedOnLocal));
|
||||
|
||||
NotificationChannel channel =
|
||||
new NotificationChannel("id", "name", IMPORTANCE_LOW);
|
||||
@@ -530,10 +534,10 @@ public class PreferencesHelperTest extends UiServiceTestCase {
|
||||
@Test
|
||||
public void testRestoreXml_withNonExistentCanonicalizedSoundUri() throws Exception {
|
||||
Thread.sleep(3000);
|
||||
when(mTestIContentProvider.canonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
|
||||
.thenReturn(null);
|
||||
when(mTestIContentProvider.uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI)))
|
||||
.thenReturn(null);
|
||||
doReturn(null)
|
||||
.when(mTestIContentProvider).canonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
|
||||
doReturn(null)
|
||||
.when(mTestIContentProvider).uncanonicalize(any(), any(), eq(CANONICAL_SOUND_URI));
|
||||
|
||||
NotificationChannel channel =
|
||||
new NotificationChannel("id", "name", IMPORTANCE_LOW);
|
||||
@@ -557,7 +561,8 @@ public class PreferencesHelperTest extends UiServiceTestCase {
|
||||
@Test
|
||||
public void testRestoreXml_withUncanonicalizedNonLocalSoundUri() throws Exception {
|
||||
// Not a local uncanonicalized uri, simulating that it fails to exist locally
|
||||
when(mTestIContentProvider.canonicalize(any(), any(), eq(SOUND_URI))).thenReturn(null);
|
||||
doReturn(null)
|
||||
.when(mTestIContentProvider).canonicalize(any(), any(), eq(SOUND_URI));
|
||||
String id = "id";
|
||||
String backupWithUncanonicalizedSoundUri = "<ranking version=\"1\">\n"
|
||||
+ "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n"
|
||||
|
||||
@@ -155,6 +155,12 @@ public class MockContentProvider extends ContentProvider {
|
||||
return MockContentProvider.this.canonicalize(uri);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void canonicalizeAsync(String callingPkg, String featureId, Uri uri,
|
||||
RemoteCallback callback) {
|
||||
MockContentProvider.this.canonicalizeAsync(uri, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri uncanonicalize(String callingPkg, @Nullable String featureId, Uri uri)
|
||||
throws RemoteException {
|
||||
@@ -289,6 +295,18 @@ public class MockContentProvider extends ContentProvider {
|
||||
throw new UnsupportedOperationException("unimplemented mock method call");
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public void canonicalizeAsync(Uri uri, RemoteCallback callback) {
|
||||
AsyncTask.SERIAL_EXECUTOR.execute(() -> {
|
||||
final Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(ContentResolver.REMOTE_CALLBACK_RESULT, canonicalize(uri));
|
||||
callback.sendResult(bundle);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
|
||||
@@ -145,11 +145,22 @@ public class MockIContentProvider implements IContentProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri canonicalize(String callingPkg, @Nullable String featureId, Uri uri)
|
||||
throws RemoteException {
|
||||
public Uri canonicalize(String callingPkg, @Nullable String featureId, Uri uri) {
|
||||
throw new UnsupportedOperationException("unimplemented mock method");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void canonicalizeAsync(String callingPkg, String featureId, Uri uri,
|
||||
RemoteCallback remoteCallback) {
|
||||
AsyncTask.SERIAL_EXECUTOR.execute(() -> {
|
||||
final Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(ContentResolver.REMOTE_CALLBACK_RESULT,
|
||||
canonicalize(callingPkg, featureId, uri));
|
||||
remoteCallback.sendResult(bundle);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri uncanonicalize(String callingPkg, @Nullable String featureId, Uri uri)
|
||||
throws RemoteException {
|
||||
|
||||
Reference in New Issue
Block a user