diff options
Diffstat (limited to 'core/java')
-rw-r--r-- | core/java/android/provider/DocumentsContract.java | 51 | ||||
-rw-r--r-- | core/java/android/provider/DocumentsProvider.java | 97 |
2 files changed, 127 insertions, 21 deletions
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index b907375..6b8e2de 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -287,6 +287,16 @@ public final class DocumentsContract { public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 5; /** + * Flag indicating that a document can be renamed. + * + * @see #COLUMN_FLAGS + * @see DocumentsContract#renameDocument(ContentProviderClient, Uri, + * String) + * @see DocumentsProvider#renameDocument(String, String) + */ + public static final int FLAG_SUPPORTS_RENAME = 1 << 6; + + /** * Flag indicating that document titles should be hidden when viewing * this directory in a larger format grid. For example, a directory * containing only images may want the image thumbnails to speak for @@ -494,6 +504,8 @@ public final class DocumentsContract { /** {@hide} */ public static final String METHOD_CREATE_DOCUMENT = "android:createDocument"; /** {@hide} */ + public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument"; + /** {@hide} */ public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument"; /** {@hide} */ @@ -898,6 +910,45 @@ public final class DocumentsContract { } /** + * Change the display name of an existing document. + * <p> + * If the underlying provider needs to create a new + * {@link Document#COLUMN_DOCUMENT_ID} to represent the updated display + * name, that new document is returned and the original document is no + * longer valid. Otherwise, the original document is returned. + * + * @param documentUri document with {@link Document#FLAG_SUPPORTS_RENAME} + * @param displayName updated name for document + * @return the existing or new document after the rename, or {@code null} if + * failed. + */ + public static Uri renameDocument(ContentResolver resolver, Uri documentUri, + String displayName) { + final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( + documentUri.getAuthority()); + try { + return renameDocument(client, documentUri, displayName); + } catch (Exception e) { + Log.w(TAG, "Failed to rename document", e); + return null; + } finally { + ContentProviderClient.releaseQuietly(client); + } + } + + /** {@hide} */ + public static Uri renameDocument(ContentProviderClient client, Uri documentUri, + String displayName) throws RemoteException { + final Bundle in = new Bundle(); + in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); + in.putString(Document.COLUMN_DISPLAY_NAME, displayName); + + final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in); + final Uri outUri = out.getParcelable(DocumentsContract.EXTRA_URI); + return (outUri != null) ? outUri : documentUri; + } + + /** * Delete the given document. * * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE} diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index 1a7a00f2..066b4aa 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -19,9 +19,11 @@ package android.provider; import static android.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE; import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT; import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT; +import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT; import static android.provider.DocumentsContract.getDocumentId; import static android.provider.DocumentsContract.getRootId; import static android.provider.DocumentsContract.getSearchDocumentsQuery; +import static android.provider.DocumentsContract.isViaUri; import android.content.ContentProvider; import android.content.ContentResolver; @@ -206,7 +208,7 @@ public abstract class DocumentsProvider extends ContentProvider { * If the MIME type is not supported, the provider must throw. * @param displayName the display name of the new document. The provider may * alter this name to meet any internal constraints, such as - * conflicting names. + * avoiding conflicting names. */ @SuppressWarnings("unused") public String createDocument(String parentDocumentId, String mimeType, String displayName) @@ -215,11 +217,33 @@ public abstract class DocumentsProvider extends ContentProvider { } /** - * Delete the requested document. Upon returning, any URI permission grants - * for the given document will be revoked. If additional documents were - * deleted as a side effect of this call (such as documents inside a - * directory) the implementor is responsible for revoking those permissions - * using {@link #revokeDocumentPermission(String)}. + * Rename an existing document. + * <p> + * If a different {@link Document#COLUMN_DOCUMENT_ID} must be used to + * represent the renamed document, generate and return it. Any outstanding + * URI permission grants will be updated to point at the new document. If + * the original {@link Document#COLUMN_DOCUMENT_ID} is still valid after the + * rename, return {@code null}. + * + * @param documentId the document to rename. + * @param displayName the updated display name of the document. The provider + * may alter this name to meet any internal constraints, such as + * avoiding conflicting names. + */ + @SuppressWarnings("unused") + public String renameDocument(String documentId, String displayName) + throws FileNotFoundException { + throw new UnsupportedOperationException("Rename not supported"); + } + + /** + * Delete the requested document. + * <p> + * Upon returning, any URI permission grants for the given document will be + * revoked. If additional documents were deleted as a side effect of this + * call (such as documents inside a directory) the implementor is + * responsible for revoking those permissions using + * {@link #revokeDocumentPermission(String)}. * * @param documentId the document to delete. */ @@ -523,26 +547,33 @@ public abstract class DocumentsProvider extends ContentProvider { DocumentsContract.getDocumentId(uri)); // Caller may only have prefix grant, so extend them a grant to - // the narrow Uri. Caller already holds read grant to get here, - // so check for any other modes we should extend. - int modeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION; - if (context.checkCallingOrSelfUriPermission(uri, - Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - == PackageManager.PERMISSION_GRANTED) { - modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION; - } - if (context.checkCallingOrSelfUriPermission(uri, - Intent.FLAG_GRANT_READ_URI_PERMISSION - | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) - == PackageManager.PERMISSION_GRANTED) { - modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION; - } + // the narrow URI. + final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, uri); context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags); return narrowUri; } return null; } + private static int getCallingOrSelfUriPermissionModeFlags(Context context, Uri uri) { + // TODO: move this to a direct AMS call + int modeFlags = 0; + if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION) + == PackageManager.PERMISSION_GRANTED) { + modeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION; + } + if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + == PackageManager.PERMISSION_GRANTED) { + modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION; + } + if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + == PackageManager.PERMISSION_GRANTED) { + modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION; + } + return modeFlags; + } + /** * Implementation is provided by the parent class. Throws by default, and * cannot be overriden. @@ -588,6 +619,7 @@ public abstract class DocumentsProvider extends ContentProvider { return super.call(method, arg, extras); } + final Context context = getContext(); final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI); final String authority = documentUri.getAuthority(); final String documentId = DocumentsContract.getDocumentId(documentUri); @@ -605,7 +637,6 @@ public abstract class DocumentsProvider extends ContentProvider { final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE); final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); - final String newDocumentId = createDocument(documentId, mimeType, displayName); // No need to issue new grants here, since caller either has @@ -615,6 +646,30 @@ public abstract class DocumentsProvider extends ContentProvider { newDocumentId); out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); + } else if (METHOD_RENAME_DOCUMENT.equals(method)) { + enforceWritePermissionInner(documentUri); + + final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); + final String newDocumentId = renameDocument(documentId, displayName); + + if (newDocumentId != null) { + final Uri newDocumentUri = DocumentsContract.buildDocumentMaybeViaUri( + documentUri, newDocumentId); + + // If caller came in with a narrow grant, issue them a + // narrow grant for the newly renamed document. + if (!isViaUri(newDocumentUri)) { + final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, + documentUri); + context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags); + } + + out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); + + // Original document no longer exists, clean up any grants + revokeDocumentPermission(documentId); + } + } else if (METHOD_DELETE_DOCUMENT.equals(method)) { enforceWritePermissionInner(documentUri); deleteDocument(documentId); |