Merge "Support for renaming documents."

This commit is contained in:
Jeff Sharkey
2014-05-22 05:45:29 +00:00
committed by Android (Google) Code Review
4 changed files with 155 additions and 22 deletions

View File

@@ -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[]);
}

View File

@@ -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.
*

View File

@@ -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);

View File

@@ -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);
}