Add async version of getProviderMimeType
Fixes: b/147646960 Test: atest FrameworksCoreTests:android.content.ContentResolverTest Change-Id: I04c15ac008fe14b215f954af150226dc94f22232
This commit is contained in:
@@ -303,8 +303,13 @@ interface IActivityManager {
|
||||
boolean isTopActivityImmersive();
|
||||
void crashApplication(int uid, int initialPid, in String packageName, int userId,
|
||||
in String message, boolean force);
|
||||
@UnsupportedAppUsage
|
||||
/** @deprecated -- use getProviderMimeTypeAsync */
|
||||
@UnsupportedAppUsage(maxTargetSdk = 29, publicAlternatives =
|
||||
"Use {@link android.content.ContentResolver#getType} public API instead.")
|
||||
String getProviderMimeType(in Uri uri, int userId);
|
||||
|
||||
oneway void getProviderMimeTypeAsync(in Uri uri, int userId, in RemoteCallback resultCallback);
|
||||
|
||||
// Cause the specified process to dump the specified heap.
|
||||
boolean dumpHeap(in String process, int userId, boolean managed, boolean mallocInfo,
|
||||
boolean runGc, in String path, in ParcelFileDescriptor fd,
|
||||
|
||||
@@ -49,6 +49,7 @@ import android.os.IBinder;
|
||||
import android.os.ICancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteCallback;
|
||||
import android.os.RemoteException;
|
||||
import android.os.Trace;
|
||||
import android.os.UserHandle;
|
||||
@@ -299,6 +300,13 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getTypeAsync(Uri uri, RemoteCallback callback) {
|
||||
final Bundle result = new Bundle();
|
||||
result.putString(ContentResolver.REMOTE_CALLBACK_RESULT, getType(uri));
|
||||
callback.sendResult(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(String callingPkg, @Nullable String featureId, Uri uri,
|
||||
ContentValues initialValues, Bundle extras) {
|
||||
|
||||
@@ -33,6 +33,7 @@ import android.os.ICancellationSignal;
|
||||
import android.os.Parcel;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Parcelable;
|
||||
import android.os.RemoteCallback;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -146,6 +147,14 @@ abstract public class ContentProviderNative extends Binder implements IContentPr
|
||||
return true;
|
||||
}
|
||||
|
||||
case GET_TYPE_ASYNC_TRANSACTION: {
|
||||
data.enforceInterface(IContentProvider.descriptor);
|
||||
Uri url = Uri.CREATOR.createFromParcel(data);
|
||||
RemoteCallback callback = RemoteCallback.CREATOR.createFromParcel(data);
|
||||
getTypeAsync(url, callback);
|
||||
return true;
|
||||
}
|
||||
|
||||
case INSERT_TRANSACTION:
|
||||
{
|
||||
data.enforceInterface(IContentProvider.descriptor);
|
||||
@@ -494,6 +503,22 @@ final class ContentProviderProxy implements IContentProvider
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
/* oneway */ public void getTypeAsync(Uri uri, RemoteCallback callback) throws RemoteException {
|
||||
Parcel data = Parcel.obtain();
|
||||
try {
|
||||
data.writeInterfaceToken(IContentProvider.descriptor);
|
||||
|
||||
uri.writeToParcel(data, 0);
|
||||
callback.writeToParcel(data, 0);
|
||||
|
||||
mRemote.transact(IContentProvider.GET_TYPE_ASYNC_TRANSACTION, data, null,
|
||||
IBinder.FLAG_ONEWAY);
|
||||
} finally {
|
||||
data.recycle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(String callingPkg, @Nullable String featureId, Uri url,
|
||||
ContentValues values, Bundle extras) throws RemoteException
|
||||
|
||||
@@ -55,6 +55,7 @@ import android.os.IBinder;
|
||||
import android.os.ICancellationSignal;
|
||||
import android.os.OperationCanceledException;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteCallback;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.SystemClock;
|
||||
@@ -67,6 +68,7 @@ import android.util.Log;
|
||||
import android.util.Size;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.util.MimeIconUtils;
|
||||
|
||||
import dalvik.system.CloseGuard;
|
||||
@@ -695,6 +697,9 @@ public abstract class ContentResolver implements ContentInterface {
|
||||
private static final int SLOW_THRESHOLD_MILLIS = 500;
|
||||
private final Random mRandom = new Random(); // guarded by itself
|
||||
|
||||
/** @hide */
|
||||
public static final String REMOTE_CALLBACK_RESULT = "result";
|
||||
|
||||
public ContentResolver(@Nullable Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
@@ -807,7 +812,10 @@ public abstract class ContentResolver implements ContentInterface {
|
||||
IContentProvider provider = acquireExistingProvider(url);
|
||||
if (provider != null) {
|
||||
try {
|
||||
return provider.getType(url);
|
||||
final GetTypeResultListener resultListener = new GetTypeResultListener();
|
||||
provider.getTypeAsync(url, new RemoteCallback(resultListener));
|
||||
resultListener.waitForResult();
|
||||
return resultListener.type;
|
||||
} catch (RemoteException e) {
|
||||
// Arbitrary and not worth documenting, as Activity
|
||||
// Manager will kill this process shortly anyway.
|
||||
@@ -825,17 +833,53 @@ public abstract class ContentResolver implements ContentInterface {
|
||||
}
|
||||
|
||||
try {
|
||||
String type = ActivityManager.getService().getProviderMimeType(
|
||||
ContentProvider.getUriWithoutUserId(url), resolveUserId(url));
|
||||
return type;
|
||||
GetTypeResultListener resultListener = new GetTypeResultListener();
|
||||
ActivityManager.getService().getProviderMimeTypeAsync(
|
||||
ContentProvider.getUriWithoutUserId(url),
|
||||
resolveUserId(url),
|
||||
new RemoteCallback(resultListener));
|
||||
resultListener.waitForResult();
|
||||
return resultListener.type;
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
// We just failed to send a oneway request to the System Server. Nothing to do.
|
||||
return null;
|
||||
} catch (java.lang.Exception e) {
|
||||
Log.w(TAG, "Failed to get type for: " + url + " (" + e.getMessage() + ")");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int GET_TYPE_TIMEOUT_MILLIS = 3000;
|
||||
|
||||
private static class GetTypeResultListener implements RemoteCallback.OnResultListener {
|
||||
@GuardedBy("this")
|
||||
public boolean done;
|
||||
|
||||
@GuardedBy("this")
|
||||
public String type;
|
||||
|
||||
@Override
|
||||
public void onResult(Bundle result) {
|
||||
synchronized (this) {
|
||||
type = result.getString(REMOTE_CALLBACK_RESULT);
|
||||
done = true;
|
||||
notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void waitForResult() {
|
||||
synchronized (this) {
|
||||
if (!done) {
|
||||
try {
|
||||
wait(GET_TYPE_TIMEOUT_MILLIS);
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query for the possible MIME types for the representations the given
|
||||
* content URL can be returned when opened as as stream with
|
||||
|
||||
@@ -27,6 +27,7 @@ import android.os.IBinder;
|
||||
import android.os.ICancellationSignal;
|
||||
import android.os.IInterface;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteCallback;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -42,6 +43,14 @@ public interface IContentProvider extends IInterface {
|
||||
@Nullable Bundle queryArgs, @Nullable ICancellationSignal cancellationSignal)
|
||||
throws RemoteException;
|
||||
public String getType(Uri url) throws RemoteException;
|
||||
|
||||
/**
|
||||
* An 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.
|
||||
*/
|
||||
void getTypeAsync(Uri uri, RemoteCallback callback) throws RemoteException;
|
||||
|
||||
@Deprecated
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q, publicAlternatives = "Use {@link "
|
||||
+ "ContentProviderClient#insert(android.net.Uri, android.content.ContentValues)} "
|
||||
@@ -152,4 +161,5 @@ public interface IContentProvider extends IInterface {
|
||||
static final int UNCANONICALIZE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 25;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1315,6 +1315,17 @@
|
||||
android:process=":RedactingProvider">
|
||||
</provider>
|
||||
|
||||
<provider
|
||||
android:name="android.content.FakeProviderLocal"
|
||||
android:authorities="android.content.FakeProviderLocal">
|
||||
</provider>
|
||||
|
||||
<provider
|
||||
android:name="android.content.FakeProviderRemote"
|
||||
android:authorities="android.content.FakeProviderRemote"
|
||||
android:process=":FakeProvider">
|
||||
</provider>
|
||||
|
||||
<!-- Application components used for os tests -->
|
||||
|
||||
<service android:name="android.os.MessengerService"
|
||||
|
||||
@@ -191,4 +191,22 @@ public class ContentResolverTest {
|
||||
assertEquals(uri, ContentResolver
|
||||
.translateDeprecatedDataPath(ContentResolver.translateDeprecatedDataPath(uri)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetType_localProvider() {
|
||||
// This provider is running in the same process as the test and is already registered with
|
||||
// the ContentResolver when the application starts, see
|
||||
// ActivityThread#installContentProviders. This allows ContentResolver to follow a
|
||||
// streamlined code path.
|
||||
String type = mResolver.getType(Uri.parse("content://android.content.FakeProviderLocal"));
|
||||
assertEquals("fake/local", type);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetType_remoteProvider() {
|
||||
// This provider is running in a different process, which will need to be started
|
||||
// in order to acquire the provider
|
||||
String type = mResolver.getType(Uri.parse("content://android.content.FakeProviderRemote"));
|
||||
assertEquals("fake/remote", type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.content;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* A dummy content provider for tests. This provider runs in the same process as the test.
|
||||
*/
|
||||
public class FakeProviderLocal extends ContentProvider {
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
||||
String sortOrder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
return "fake/local";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.content;
|
||||
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
/**
|
||||
* A dummy content provider for tests. This provider runs in a different process from the test.
|
||||
*/
|
||||
public class FakeProviderRemote extends ContentProvider {
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
|
||||
String sortOrder) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
return "fake/remote";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -7786,7 +7786,10 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
*
|
||||
* Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/
|
||||
* src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
|
||||
*
|
||||
* @deprecated -- use getProviderMimeTypeAsync.
|
||||
*/
|
||||
@Deprecated
|
||||
public String getProviderMimeType(Uri uri, int userId) {
|
||||
enforceNotIsolatedCaller("getProviderMimeType");
|
||||
final String name = uri.getAuthority();
|
||||
@@ -7847,6 +7850,43 @@ public class ActivityManagerService extends IActivityManager.Stub
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows apps to retrieve the MIME type of a URI.
|
||||
* If an app is in the same user as the ContentProvider, or if it is allowed to interact across
|
||||
* users, then it does not need permission to access the ContentProvider.
|
||||
* Either way, it needs cross-user uri grants.
|
||||
*/
|
||||
@Override
|
||||
public void getProviderMimeTypeAsync(Uri uri, int userId, RemoteCallback resultCallback) {
|
||||
enforceNotIsolatedCaller("getProviderMimeTypeAsync");
|
||||
final String name = uri.getAuthority();
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
final int callingPid = Binder.getCallingPid();
|
||||
final int safeUserId = mUserController.unsafeConvertIncomingUser(userId);
|
||||
final long ident = canClearIdentity(callingPid, callingUid, userId)
|
||||
? Binder.clearCallingIdentity() : 0;
|
||||
try {
|
||||
final ContentProviderHolder holder = getContentProviderExternalUnchecked(name, null,
|
||||
callingUid, "*getmimetype*", safeUserId);
|
||||
if (holder != null) {
|
||||
holder.provider.getTypeAsync(uri, new RemoteCallback(result -> {
|
||||
final long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
removeContentProviderExternalUnchecked(name, null, safeUserId);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
}
|
||||
resultCallback.sendResult(result);
|
||||
}));
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Content provider dead retrieving " + uri, e);
|
||||
resultCallback.sendResult(Bundle.EMPTY);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
}
|
||||
|
||||
int checkContentProviderUriPermission(Uri uri, int userId, int callingUid, int modeFlags) {
|
||||
final String name = uri.getAuthority();
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentProviderResult;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.IContentProvider;
|
||||
@@ -31,10 +32,12 @@ import android.content.pm.ProviderInfo;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.ICancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteCallback;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -80,6 +83,11 @@ public class MockContentProvider extends ContentProvider {
|
||||
return MockContentProvider.this.getType(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getTypeAsync(Uri uri, RemoteCallback callback) throws RemoteException {
|
||||
MockContentProvider.this.getTypeAsync(uri, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(String callingPackage, @Nullable String featureId, Uri url,
|
||||
ContentValues initialValues, Bundle extras) throws RemoteException {
|
||||
@@ -212,6 +220,18 @@ public class MockContentProvider extends ContentProvider {
|
||||
throw new UnsupportedOperationException("unimplemented mock method");
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public void getTypeAsync(Uri uri, RemoteCallback remoteCallback) {
|
||||
AsyncTask.SERIAL_EXECUTOR.execute(() -> {
|
||||
final Bundle bundle = new Bundle();
|
||||
bundle.putString(ContentResolver.REMOTE_CALLBACK_RESULT, getType(uri));
|
||||
remoteCallback.sendResult(bundle);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
throw new UnsupportedOperationException("unimplemented mock method");
|
||||
|
||||
@@ -19,16 +19,19 @@ package android.test.mock;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentProviderResult;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.EntityIterator;
|
||||
import android.content.IContentProvider;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.os.ICancellationSignal;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.RemoteCallback;
|
||||
import android.os.RemoteException;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -60,6 +63,16 @@ public class MockIContentProvider implements IContentProvider {
|
||||
throw new UnsupportedOperationException("unimplemented mock method");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void getTypeAsync(Uri uri, RemoteCallback remoteCallback) {
|
||||
AsyncTask.SERIAL_EXECUTOR.execute(() -> {
|
||||
final Bundle bundle = new Bundle();
|
||||
bundle.putString(ContentResolver.REMOTE_CALLBACK_RESULT, getType(uri));
|
||||
remoteCallback.sendResult(bundle);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unused")
|
||||
public Uri insert(String callingPackage, @Nullable String featureId, Uri url,
|
||||
|
||||
Reference in New Issue
Block a user