Merge "Support for renaming documents."
This commit is contained in:
@@ -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[]);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user