Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9f561c36 authored by Jeff Sharkey's avatar Jeff Sharkey Committed by Android (Google) Code Review
Browse files

Merge "Support for renaming documents."

parents 842dd77b b7e1255d
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -23971,6 +23971,7 @@ package android.provider {
    method public static java.lang.String getSearchDocumentsQuery(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 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_INFO = "info";
    field public static final java.lang.String EXTRA_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_SUPPORTS_CREATE = 8; // 0x8
    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_WRITE = 2; // 0x2
    field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory";
@@ -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 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 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 int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
  }
+51 −0
Original line number Diff line number Diff line
@@ -286,6 +286,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
@@ -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} */
@@ -897,6 +909,45 @@ public final class DocumentsContract {
        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.
     *
+76 −21
Original line number Diff line number Diff line
@@ -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,24 +547,31 @@ 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)
                // 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
        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;
        }
                context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags);
                return narrowUri;
        }
        return null;
        return modeFlags;
    }

    /**
@@ -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);
+25 −1
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsProvider;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;

@@ -239,9 +240,12 @@ public class ExternalStorageProvider extends DocumentsProvider {
        if (file.canWrite()) {
            if (file.isDirectory()) {
                flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
                flags |= Document.FLAG_SUPPORTS_DELETE;
                flags |= Document.FLAG_SUPPORTS_RENAME;
            } else {
                flags |= Document.FLAG_SUPPORTS_WRITE;
                flags |= Document.FLAG_SUPPORTS_DELETE;
                flags |= Document.FLAG_SUPPORTS_RENAME;
            }
        }

@@ -331,10 +335,30 @@ public class ExternalStorageProvider extends DocumentsProvider {
        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
    public void deleteDocument(String docId) throws FileNotFoundException {
        // TODO: extend to delete directories
        final File file = getFileForDocId(docId);
        if (file.isDirectory()) {
            FileUtils.deleteContents(file);
        }
        if (!file.delete()) {
            throw new IllegalStateException("Failed to delete " + file);
        }