diff options
author | Jeff Sharkey <jsharkey@android.com> | 2018-10-22 18:01:27 -0600 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2018-10-24 19:34:09 -0600 |
commit | 643e99ef060063c2cee2758ce6398d1f7a72b5e0 (patch) | |
tree | c8c331b1a972b4a81cb7dff6078df2fd7da0f602 | |
parent | c72a391e6369ec983f31909f76a2bb3598c0bb3c (diff) |
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
5 files changed, 80 insertions, 72 deletions
diff --git a/api/current.txt b/api/current.txt index 1c3ee82dbba0..7f356af4bf2f 100755 --- a/api/current.txt +++ b/api/current.txt @@ -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"; diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 954d18abc6e1..67e52aad9206 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -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) diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index f5660b950c0a..57f33f02ac42 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -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 { - - 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 { + /** + * 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( - 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(); } } } diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml index 1072f95371a0..484dbccca9a8 100644 --- a/packages/ExternalStorageProvider/AndroidManifest.xml +++ b/packages/ExternalStorageProvider/AndroidManifest.xml @@ -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"> diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 62207c5517ec..4e52ff6d016c 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -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); |