Loading api/current.txt +3 −0 Original line number Original line Diff line number Diff line Loading @@ -23971,6 +23971,7 @@ package android.provider { method public static java.lang.String getSearchDocumentsQuery(android.net.Uri); method public static java.lang.String getSearchDocumentsQuery(android.net.Uri); method public static java.lang.String getViaDocumentId(android.net.Uri); method public static java.lang.String getViaDocumentId(android.net.Uri); method public static boolean isDocumentUri(android.content.Context, android.net.Uri); method public static boolean isDocumentUri(android.content.Context, android.net.Uri); method public static android.net.Uri renameDocument(android.content.ContentResolver, android.net.Uri, java.lang.String); field public static final java.lang.String EXTRA_ERROR = "error"; field public static final java.lang.String EXTRA_ERROR = "error"; field public static final java.lang.String EXTRA_INFO = "info"; field public static final java.lang.String EXTRA_INFO = "info"; field public static final java.lang.String EXTRA_LOADING = "loading"; field public static final java.lang.String EXTRA_LOADING = "loading"; Loading @@ -23990,6 +23991,7 @@ package android.provider { field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20 field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20 field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8 field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8 field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4 field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4 field public static final int FLAG_SUPPORTS_RENAME = 64; // 0x40 field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1 field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1 field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2 field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2 field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory"; field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory"; Loading Loading @@ -24034,6 +24036,7 @@ package android.provider { method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException; method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException; method public android.database.Cursor querySearchDocuments(java.lang.String, java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; method public android.database.Cursor querySearchDocuments(java.lang.String, java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; method public java.lang.String renameDocument(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; method public final void revokeDocumentPermission(java.lang.String); method public final void revokeDocumentPermission(java.lang.String); method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]); method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]); } } core/java/android/provider/DocumentsContract.java +51 −0 Original line number Original line Diff line number Diff line Loading @@ -286,6 +286,16 @@ public final class DocumentsContract { */ */ public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 5; 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 * Flag indicating that document titles should be hidden when viewing * this directory in a larger format grid. For example, a directory * this directory in a larger format grid. For example, a directory Loading Loading @@ -494,6 +504,8 @@ public final class DocumentsContract { /** {@hide} */ /** {@hide} */ public static final String METHOD_CREATE_DOCUMENT = "android:createDocument"; public static final String METHOD_CREATE_DOCUMENT = "android:createDocument"; /** {@hide} */ /** {@hide} */ public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument"; /** {@hide} */ public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument"; public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument"; /** {@hide} */ /** {@hide} */ Loading Loading @@ -897,6 +909,45 @@ public final class DocumentsContract { return out.getParcelable(DocumentsContract.EXTRA_URI); return out.getParcelable(DocumentsContract.EXTRA_URI); } } /** * 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. * Delete the given document. * * Loading core/java/android/provider/DocumentsProvider.java +76 −21 Original line number Original line Diff line number Diff line Loading @@ -19,9 +19,11 @@ package android.provider; import static android.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE; import static android.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE; import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT; import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT; import static android.provider.DocumentsContract.METHOD_DELETE_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.getDocumentId; import static android.provider.DocumentsContract.getRootId; import static android.provider.DocumentsContract.getRootId; import static android.provider.DocumentsContract.getSearchDocumentsQuery; import static android.provider.DocumentsContract.getSearchDocumentsQuery; import static android.provider.DocumentsContract.isViaUri; import android.content.ContentProvider; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentResolver; Loading Loading @@ -206,7 +208,7 @@ public abstract class DocumentsProvider extends ContentProvider { * If the MIME type is not supported, the provider must throw. * If the MIME type is not supported, the provider must throw. * @param displayName the display name of the new document. The provider may * @param displayName the display name of the new document. The provider may * alter this name to meet any internal constraints, such as * alter this name to meet any internal constraints, such as * conflicting names. * avoiding conflicting names. */ */ @SuppressWarnings("unused") @SuppressWarnings("unused") public String createDocument(String parentDocumentId, String mimeType, String displayName) public String createDocument(String parentDocumentId, String mimeType, String displayName) Loading @@ -215,11 +217,33 @@ public abstract class DocumentsProvider extends ContentProvider { } } /** /** * Delete the requested document. Upon returning, any URI permission grants * Rename an existing document. * for the given document will be revoked. If additional documents were * <p> * deleted as a side effect of this call (such as documents inside a * If a different {@link Document#COLUMN_DOCUMENT_ID} must be used to * directory) the implementor is responsible for revoking those permissions * represent the renamed document, generate and return it. Any outstanding * using {@link #revokeDocumentPermission(String)}. * 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. * @param documentId the document to delete. */ */ Loading Loading @@ -523,24 +547,31 @@ public abstract class DocumentsProvider extends ContentProvider { DocumentsContract.getDocumentId(uri)); DocumentsContract.getDocumentId(uri)); // Caller may only have prefix grant, so extend them a grant to // Caller may only have prefix grant, so extend them a grant to // the narrow Uri. Caller already holds read grant to get here, // the narrow URI. // so check for any other modes we should extend. final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, uri); int modeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION; context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags); if (context.checkCallingOrSelfUriPermission(uri, return narrowUri; Intent.FLAG_GRANT_WRITE_URI_PERMISSION) } 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) { == PackageManager.PERMISSION_GRANTED) { modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION; modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION; } } if (context.checkCallingOrSelfUriPermission(uri, if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) == PackageManager.PERMISSION_GRANTED) { == PackageManager.PERMISSION_GRANTED) { modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION; modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION; } } context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags); return modeFlags; return narrowUri; } return null; } } /** /** Loading Loading @@ -588,6 +619,7 @@ public abstract class DocumentsProvider extends ContentProvider { return super.call(method, arg, extras); return super.call(method, arg, extras); } } final Context context = getContext(); final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI); final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI); final String authority = documentUri.getAuthority(); final String authority = documentUri.getAuthority(); final String documentId = DocumentsContract.getDocumentId(documentUri); final String documentId = DocumentsContract.getDocumentId(documentUri); Loading @@ -605,7 +637,6 @@ public abstract class DocumentsProvider extends ContentProvider { final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE); final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE); final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); final String newDocumentId = createDocument(documentId, mimeType, displayName); final String newDocumentId = createDocument(documentId, mimeType, displayName); // No need to issue new grants here, since caller either has // No need to issue new grants here, since caller either has Loading @@ -615,6 +646,30 @@ public abstract class DocumentsProvider extends ContentProvider { newDocumentId); newDocumentId); out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); 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)) { } else if (METHOD_DELETE_DOCUMENT.equals(method)) { enforceWritePermissionInner(documentUri); enforceWritePermissionInner(documentUri); deleteDocument(documentId); deleteDocument(documentId); Loading packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +25 −1 Original line number Original line Diff line number Diff line Loading @@ -35,6 +35,7 @@ import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.provider.DocumentsContract.Root; import android.provider.DocumentsProvider; import android.provider.DocumentsProvider; import android.text.TextUtils; import android.util.Log; import android.util.Log; import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap; Loading Loading @@ -239,9 +240,12 @@ public class ExternalStorageProvider extends DocumentsProvider { if (file.canWrite()) { if (file.canWrite()) { if (file.isDirectory()) { if (file.isDirectory()) { flags |= Document.FLAG_DIR_SUPPORTS_CREATE; flags |= Document.FLAG_DIR_SUPPORTS_CREATE; flags |= Document.FLAG_SUPPORTS_DELETE; flags |= Document.FLAG_SUPPORTS_RENAME; } else { } else { flags |= Document.FLAG_SUPPORTS_WRITE; flags |= Document.FLAG_SUPPORTS_WRITE; flags |= Document.FLAG_SUPPORTS_DELETE; flags |= Document.FLAG_SUPPORTS_DELETE; flags |= Document.FLAG_SUPPORTS_RENAME; } } } } Loading Loading @@ -331,10 +335,30 @@ public class ExternalStorageProvider extends DocumentsProvider { return getDocIdForFile(file); return getDocIdForFile(file); } } @Override public String renameDocument(String docId, String displayName) throws FileNotFoundException { final File before = getFileForDocId(docId); final File after = new File(before.getParentFile(), displayName); if (after.exists()) { throw new IllegalStateException("Already exists " + after); } if (!before.renameTo(after)) { throw new IllegalStateException("Failed to rename to " + after); } final String afterDocId = getDocIdForFile(after); if (!TextUtils.equals(docId, afterDocId)) { return afterDocId; } else { return null; } } @Override @Override public void deleteDocument(String docId) throws FileNotFoundException { public void deleteDocument(String docId) throws FileNotFoundException { // TODO: extend to delete directories final File file = getFileForDocId(docId); final File file = getFileForDocId(docId); if (file.isDirectory()) { FileUtils.deleteContents(file); } if (!file.delete()) { if (!file.delete()) { throw new IllegalStateException("Failed to delete " + file); throw new IllegalStateException("Failed to delete " + file); } } Loading Loading
api/current.txt +3 −0 Original line number Original line Diff line number Diff line Loading @@ -23971,6 +23971,7 @@ package android.provider { method public static java.lang.String getSearchDocumentsQuery(android.net.Uri); method public static java.lang.String getSearchDocumentsQuery(android.net.Uri); method public static java.lang.String getViaDocumentId(android.net.Uri); method public static java.lang.String getViaDocumentId(android.net.Uri); method public static boolean isDocumentUri(android.content.Context, android.net.Uri); method public static boolean isDocumentUri(android.content.Context, android.net.Uri); method public static android.net.Uri renameDocument(android.content.ContentResolver, android.net.Uri, java.lang.String); field public static final java.lang.String EXTRA_ERROR = "error"; field public static final java.lang.String EXTRA_ERROR = "error"; field public static final java.lang.String EXTRA_INFO = "info"; field public static final java.lang.String EXTRA_INFO = "info"; field public static final java.lang.String EXTRA_LOADING = "loading"; field public static final java.lang.String EXTRA_LOADING = "loading"; Loading @@ -23990,6 +23991,7 @@ package android.provider { field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20 field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20 field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8 field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8 field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4 field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4 field public static final int FLAG_SUPPORTS_RENAME = 64; // 0x40 field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1 field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1 field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2 field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2 field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory"; field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory"; Loading Loading @@ -24034,6 +24036,7 @@ package android.provider { method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException; method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException; method public android.database.Cursor querySearchDocuments(java.lang.String, java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; method public android.database.Cursor querySearchDocuments(java.lang.String, java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; method public java.lang.String renameDocument(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; method public final void revokeDocumentPermission(java.lang.String); method public final void revokeDocumentPermission(java.lang.String); method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]); method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]); } }
core/java/android/provider/DocumentsContract.java +51 −0 Original line number Original line Diff line number Diff line Loading @@ -286,6 +286,16 @@ public final class DocumentsContract { */ */ public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 5; 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 * Flag indicating that document titles should be hidden when viewing * this directory in a larger format grid. For example, a directory * this directory in a larger format grid. For example, a directory Loading Loading @@ -494,6 +504,8 @@ public final class DocumentsContract { /** {@hide} */ /** {@hide} */ public static final String METHOD_CREATE_DOCUMENT = "android:createDocument"; public static final String METHOD_CREATE_DOCUMENT = "android:createDocument"; /** {@hide} */ /** {@hide} */ public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument"; /** {@hide} */ public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument"; public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument"; /** {@hide} */ /** {@hide} */ Loading Loading @@ -897,6 +909,45 @@ public final class DocumentsContract { return out.getParcelable(DocumentsContract.EXTRA_URI); return out.getParcelable(DocumentsContract.EXTRA_URI); } } /** * 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. * Delete the given document. * * Loading
core/java/android/provider/DocumentsProvider.java +76 −21 Original line number Original line Diff line number Diff line Loading @@ -19,9 +19,11 @@ package android.provider; import static android.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE; import static android.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE; import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT; import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT; import static android.provider.DocumentsContract.METHOD_DELETE_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.getDocumentId; import static android.provider.DocumentsContract.getRootId; import static android.provider.DocumentsContract.getRootId; import static android.provider.DocumentsContract.getSearchDocumentsQuery; import static android.provider.DocumentsContract.getSearchDocumentsQuery; import static android.provider.DocumentsContract.isViaUri; import android.content.ContentProvider; import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentResolver; Loading Loading @@ -206,7 +208,7 @@ public abstract class DocumentsProvider extends ContentProvider { * If the MIME type is not supported, the provider must throw. * If the MIME type is not supported, the provider must throw. * @param displayName the display name of the new document. The provider may * @param displayName the display name of the new document. The provider may * alter this name to meet any internal constraints, such as * alter this name to meet any internal constraints, such as * conflicting names. * avoiding conflicting names. */ */ @SuppressWarnings("unused") @SuppressWarnings("unused") public String createDocument(String parentDocumentId, String mimeType, String displayName) public String createDocument(String parentDocumentId, String mimeType, String displayName) Loading @@ -215,11 +217,33 @@ public abstract class DocumentsProvider extends ContentProvider { } } /** /** * Delete the requested document. Upon returning, any URI permission grants * Rename an existing document. * for the given document will be revoked. If additional documents were * <p> * deleted as a side effect of this call (such as documents inside a * If a different {@link Document#COLUMN_DOCUMENT_ID} must be used to * directory) the implementor is responsible for revoking those permissions * represent the renamed document, generate and return it. Any outstanding * using {@link #revokeDocumentPermission(String)}. * 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. * @param documentId the document to delete. */ */ Loading Loading @@ -523,24 +547,31 @@ public abstract class DocumentsProvider extends ContentProvider { DocumentsContract.getDocumentId(uri)); DocumentsContract.getDocumentId(uri)); // Caller may only have prefix grant, so extend them a grant to // Caller may only have prefix grant, so extend them a grant to // the narrow Uri. Caller already holds read grant to get here, // the narrow URI. // so check for any other modes we should extend. final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, uri); int modeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION; context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags); if (context.checkCallingOrSelfUriPermission(uri, return narrowUri; Intent.FLAG_GRANT_WRITE_URI_PERMISSION) } 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) { == PackageManager.PERMISSION_GRANTED) { modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION; modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION; } } if (context.checkCallingOrSelfUriPermission(uri, if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) == PackageManager.PERMISSION_GRANTED) { == PackageManager.PERMISSION_GRANTED) { modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION; modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION; } } context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags); return modeFlags; return narrowUri; } return null; } } /** /** Loading Loading @@ -588,6 +619,7 @@ public abstract class DocumentsProvider extends ContentProvider { return super.call(method, arg, extras); return super.call(method, arg, extras); } } final Context context = getContext(); final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI); final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI); final String authority = documentUri.getAuthority(); final String authority = documentUri.getAuthority(); final String documentId = DocumentsContract.getDocumentId(documentUri); final String documentId = DocumentsContract.getDocumentId(documentUri); Loading @@ -605,7 +637,6 @@ public abstract class DocumentsProvider extends ContentProvider { final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE); final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE); final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); final String newDocumentId = createDocument(documentId, mimeType, displayName); final String newDocumentId = createDocument(documentId, mimeType, displayName); // No need to issue new grants here, since caller either has // No need to issue new grants here, since caller either has Loading @@ -615,6 +646,30 @@ public abstract class DocumentsProvider extends ContentProvider { newDocumentId); newDocumentId); out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); 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)) { } else if (METHOD_DELETE_DOCUMENT.equals(method)) { enforceWritePermissionInner(documentUri); enforceWritePermissionInner(documentUri); deleteDocument(documentId); deleteDocument(documentId); Loading
packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +25 −1 Original line number Original line Diff line number Diff line Loading @@ -35,6 +35,7 @@ import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.provider.DocumentsContract.Root; import android.provider.DocumentsProvider; import android.provider.DocumentsProvider; import android.text.TextUtils; import android.util.Log; import android.util.Log; import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap; Loading Loading @@ -239,9 +240,12 @@ public class ExternalStorageProvider extends DocumentsProvider { if (file.canWrite()) { if (file.canWrite()) { if (file.isDirectory()) { if (file.isDirectory()) { flags |= Document.FLAG_DIR_SUPPORTS_CREATE; flags |= Document.FLAG_DIR_SUPPORTS_CREATE; flags |= Document.FLAG_SUPPORTS_DELETE; flags |= Document.FLAG_SUPPORTS_RENAME; } else { } else { flags |= Document.FLAG_SUPPORTS_WRITE; flags |= Document.FLAG_SUPPORTS_WRITE; flags |= Document.FLAG_SUPPORTS_DELETE; flags |= Document.FLAG_SUPPORTS_DELETE; flags |= Document.FLAG_SUPPORTS_RENAME; } } } } Loading Loading @@ -331,10 +335,30 @@ public class ExternalStorageProvider extends DocumentsProvider { return getDocIdForFile(file); return getDocIdForFile(file); } } @Override public String renameDocument(String docId, String displayName) throws FileNotFoundException { final File before = getFileForDocId(docId); final File after = new File(before.getParentFile(), displayName); if (after.exists()) { throw new IllegalStateException("Already exists " + after); } if (!before.renameTo(after)) { throw new IllegalStateException("Failed to rename to " + after); } final String afterDocId = getDocIdForFile(after); if (!TextUtils.equals(docId, afterDocId)) { return afterDocId; } else { return null; } } @Override @Override public void deleteDocument(String docId) throws FileNotFoundException { public void deleteDocument(String docId) throws FileNotFoundException { // TODO: extend to delete directories final File file = getFileForDocId(docId); final File file = getFileForDocId(docId); if (file.isDirectory()) { FileUtils.deleteContents(file); } if (!file.delete()) { if (!file.delete()) { throw new IllegalStateException("Failed to delete " + file); throw new IllegalStateException("Failed to delete " + file); } } Loading