Reroute Uri conversions though MediaProvider.
Upcoming changes will prevent apps from reading "_data" columns directly, which is required to convert between DocumentsProvider and MediaProvider Uris. To solve this, delegate the call() through MediaProvider, where it can perform the "_data" lookup on behalf of the caller. Also add new getMediaUri() call to offer symmetry. Bug: 111960973, 117627072, 110961701 Test: atest android.provider.cts.MediaStoreUiTest Change-Id: I53c640704d86047d7a4bf1702aca027873395abf
This commit is contained in:
@@ -36791,6 +36791,7 @@ package android.provider {
|
||||
ctor public MediaStore();
|
||||
method public static android.net.Uri getDocumentUri(android.content.Context, android.net.Uri);
|
||||
method public static android.net.Uri getMediaScannerUri();
|
||||
method public static android.net.Uri getMediaUri(android.content.Context, android.net.Uri);
|
||||
method public static java.lang.String getVersion(android.content.Context);
|
||||
field public static final java.lang.String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
|
||||
field public static final java.lang.String ACTION_IMAGE_CAPTURE_SECURE = "android.media.action.IMAGE_CAPTURE_SECURE";
|
||||
|
||||
@@ -731,6 +731,8 @@ public final class DocumentsContract {
|
||||
public static final String EXTRA_PARENT_URI = "parentUri";
|
||||
/** {@hide} */
|
||||
public static final String EXTRA_URI = "uri";
|
||||
/** {@hide} */
|
||||
public static final String EXTRA_URI_PERMISSIONS = "uriPermissions";
|
||||
|
||||
/**
|
||||
* @see #createWebLinkIntent(ContentResolver, Uri, Bundle)
|
||||
|
||||
@@ -46,9 +46,6 @@ import android.util.Log;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
@@ -82,6 +79,11 @@ public final class MediaStore {
|
||||
*/
|
||||
public static final String RETRANSLATE_CALL = "update_titles";
|
||||
|
||||
/** {@hide} */
|
||||
public static final String GET_DOCUMENT_URI_CALL = "get_document_uri";
|
||||
/** {@hide} */
|
||||
public static final String GET_MEDIA_URI_CALL = "get_media_uri";
|
||||
|
||||
/**
|
||||
* This is for internal use by the media scanner only.
|
||||
* Name of the (optional) Uri parameter that determines whether to skip deleting
|
||||
@@ -2275,84 +2277,62 @@ public final class MediaStore {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a URI backed by a {@link DocumentsProvider} that points to the same media
|
||||
* file as the specified mediaUri. This allows apps who have permissions to access
|
||||
* media files in Storage Access Framework to perform file operations through that
|
||||
* on media files.
|
||||
* Return a {@link DocumentsProvider} Uri that is an equivalent to the given
|
||||
* {@link MediaStore} Uri.
|
||||
* <p>
|
||||
* Note: this method doesn't grant any URI permission. Callers need to obtain
|
||||
* permission before calling this method. One way to obtain permission is through
|
||||
* a 3-step process:
|
||||
* <ol>
|
||||
* <li>Call {@link android.os.storage.StorageManager#getStorageVolume(File)} to
|
||||
* obtain the {@link android.os.storage.StorageVolume} of a media file;</li>
|
||||
* This allows apps with Storage Access Framework permissions to convert
|
||||
* between {@link MediaStore} and {@link DocumentsProvider} Uris that refer
|
||||
* to the same underlying item. Note that this method doesn't grant any new
|
||||
* permissions; callers must already hold permissions obtained with
|
||||
* {@link Intent#ACTION_OPEN_DOCUMENT} or related APIs.
|
||||
*
|
||||
* <li>Invoke the intent returned by
|
||||
* {@link android.os.storage.StorageVolume#createAccessIntent(String)} to
|
||||
* obtain the access of the volume or one of its specific subdirectories;</li>
|
||||
*
|
||||
* <li>Check whether permission is granted and take persistent permission.</li>
|
||||
* </ol>
|
||||
* @param mediaUri the media URI which document URI is requested
|
||||
* @return the document URI
|
||||
* @param mediaUri The {@link MediaStore} Uri to convert.
|
||||
* @return An equivalent {@link DocumentsProvider} Uri. Returns {@code null}
|
||||
* if no equivalent was found.
|
||||
* @see #getMediaUri(Context, Uri)
|
||||
*/
|
||||
public static Uri getDocumentUri(Context context, Uri mediaUri) {
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions();
|
||||
|
||||
try {
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
|
||||
final String path = getFilePath(resolver, mediaUri);
|
||||
final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions();
|
||||
|
||||
return getDocumentUri(resolver, path, uriPermissions);
|
||||
try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
|
||||
final Bundle in = new Bundle();
|
||||
in.putParcelable(DocumentsContract.EXTRA_URI, mediaUri);
|
||||
in.putParcelableList(DocumentsContract.EXTRA_URI_PERMISSIONS, uriPermissions);
|
||||
final Bundle out = client.call(GET_DOCUMENT_URI_CALL, null, in);
|
||||
return out.getParcelable(DocumentsContract.EXTRA_URI);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowAsRuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
private static String getFilePath(ContentResolver resolver, Uri mediaUri)
|
||||
throws RemoteException {
|
||||
/**
|
||||
* Return a {@link MediaStore} Uri that is an equivalent to the given
|
||||
* {@link DocumentsProvider} Uri.
|
||||
* <p>
|
||||
* This allows apps with Storage Access Framework permissions to convert
|
||||
* between {@link MediaStore} and {@link DocumentsProvider} Uris that refer
|
||||
* to the same underlying item. Note that this method doesn't grant any new
|
||||
* permissions; callers must already hold permissions obtained with
|
||||
* {@link Intent#ACTION_OPEN_DOCUMENT} or related APIs.
|
||||
*
|
||||
* @param documentUri The {@link DocumentsProvider} Uri to convert.
|
||||
* @return An equivalent {@link MediaStore} Uri. Returns {@code null} if no
|
||||
* equivalent was found.
|
||||
* @see #getDocumentUri(Context, Uri)
|
||||
*/
|
||||
public static Uri getMediaUri(Context context, Uri documentUri) {
|
||||
final ContentResolver resolver = context.getContentResolver();
|
||||
final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions();
|
||||
|
||||
try (ContentProviderClient client =
|
||||
resolver.acquireUnstableContentProviderClient(AUTHORITY)) {
|
||||
final Cursor c = client.query(
|
||||
mediaUri,
|
||||
new String[]{ MediaColumns.DATA },
|
||||
null, /* selection */
|
||||
null, /* selectionArg */
|
||||
null /* sortOrder */);
|
||||
|
||||
final String path;
|
||||
try {
|
||||
if (c.getCount() == 0) {
|
||||
throw new IllegalStateException("Not found media file under URI: " + mediaUri);
|
||||
}
|
||||
|
||||
if (!c.moveToFirst()) {
|
||||
throw new IllegalStateException("Failed to move cursor to the first item.");
|
||||
}
|
||||
|
||||
path = c.getString(0);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(c);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
private static Uri getDocumentUri(
|
||||
ContentResolver resolver, String path, List<UriPermission> uriPermissions)
|
||||
throws RemoteException {
|
||||
|
||||
try (ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
|
||||
DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY)) {
|
||||
try (ContentProviderClient client = resolver.acquireContentProviderClient(AUTHORITY)) {
|
||||
final Bundle in = new Bundle();
|
||||
in.putParcelableList(
|
||||
DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY + ".extra.uriPermissions",
|
||||
uriPermissions);
|
||||
final Bundle out = client.call("getDocumentId", path, in);
|
||||
in.putParcelable(DocumentsContract.EXTRA_URI, documentUri);
|
||||
in.putParcelableList(DocumentsContract.EXTRA_URI_PERMISSIONS, uriPermissions);
|
||||
final Bundle out = client.call(GET_MEDIA_URI_CALL, null, in);
|
||||
return out.getParcelable(DocumentsContract.EXTRA_URI);
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowAsRuntimeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
<intent-filter>
|
||||
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||
</intent-filter>
|
||||
<!-- Stub that allows MediaProvider to make incoming calls -->
|
||||
<path-permission
|
||||
android:path="/media_internal"
|
||||
android:permission="android.permission.WRITE_MEDIA_STORAGE" />
|
||||
</provider>
|
||||
|
||||
<receiver android:name=".MountReceiver">
|
||||
|
||||
@@ -37,6 +37,7 @@ import android.provider.DocumentsContract;
|
||||
import android.provider.DocumentsContract.Document;
|
||||
import android.provider.DocumentsContract.Path;
|
||||
import android.provider.DocumentsContract.Root;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.Settings;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
@@ -606,11 +607,16 @@ public class ExternalStorageProvider extends FileSystemProvider {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "getDocumentId": {
|
||||
final String path = arg;
|
||||
final List<UriPermission> accessUriPermissions =
|
||||
extras.getParcelableArrayList(AUTHORITY + ".extra.uriPermissions");
|
||||
case MediaStore.GET_DOCUMENT_URI_CALL: {
|
||||
// All callers must go through MediaProvider
|
||||
getContext().enforceCallingPermission(
|
||||
android.Manifest.permission.WRITE_MEDIA_STORAGE, TAG);
|
||||
|
||||
final Uri fileUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
|
||||
final List<UriPermission> accessUriPermissions = extras
|
||||
.getParcelableArrayList(DocumentsContract.EXTRA_URI_PERMISSIONS);
|
||||
|
||||
final String path = fileUri.getPath();
|
||||
try {
|
||||
final Bundle out = new Bundle();
|
||||
final Uri uri = getDocumentUri(path, accessUriPermissions);
|
||||
@@ -619,7 +625,22 @@ public class ExternalStorageProvider extends FileSystemProvider {
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new IllegalStateException("File in " + path + " is not found.", e);
|
||||
}
|
||||
}
|
||||
case MediaStore.GET_MEDIA_URI_CALL: {
|
||||
// All callers must go through MediaProvider
|
||||
getContext().enforceCallingPermission(
|
||||
android.Manifest.permission.WRITE_MEDIA_STORAGE, TAG);
|
||||
|
||||
final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
|
||||
final String docId = DocumentsContract.getDocumentId(documentUri);
|
||||
try {
|
||||
final Bundle out = new Bundle();
|
||||
final Uri uri = Uri.fromFile(getFileForDocId(docId));
|
||||
out.putParcelable(DocumentsContract.EXTRA_URI, uri);
|
||||
return out;
|
||||
} catch (FileNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
default:
|
||||
Log.w(TAG, "unknown method passed to call(): " + method);
|
||||
|
||||
Reference in New Issue
Block a user