Merge "Add support for efficient move within a document provider."
diff --git a/api/current.txt b/api/current.txt
index dd7e326..2737792 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -26016,6 +26016,7 @@
     field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8
     field public static final int FLAG_SUPPORTS_COPY = 128; // 0x80
     field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4
+    field public static final int FLAG_SUPPORTS_MOVE = 256; // 0x100
     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
diff --git a/api/system-current.txt b/api/system-current.txt
index bbfe9bb..9df21c2 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -27990,6 +27990,7 @@
     field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8
     field public static final int FLAG_SUPPORTS_COPY = 128; // 0x80
     field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4
+    field public static final int FLAG_SUPPORTS_MOVE = 256; // 0x100
     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
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 50f3038..9eded31 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -324,13 +324,22 @@
          * within the same document provider.
          *
          * @see #COLUMN_FLAGS
-         * @see DocumentsContract#copyDocument(ContentProviderClient, Uri,
-         *      String)
-         * @see DocumentsProvider#copyDocument(String, String, String)
+         * @see DocumentsContract#copyDocument(ContentProviderClient, Uri, Uri)
+         * @see DocumentsProvider#copyDocument(String, String)
          */
         public static final int FLAG_SUPPORTS_COPY = 1 << 7;
 
         /**
+         * Flag indicating that a document can be moved to another location
+         * within the same document provider.
+         *
+         * @see #COLUMN_FLAGS
+         * @see DocumentsContract#moveDocument(ContentProviderClient, Uri, Uri)
+         * @see DocumentsProvider#moveDocument(String, String)
+         */
+        public static final int FLAG_SUPPORTS_MOVE = 1 << 8;
+
+        /**
          * 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
@@ -553,6 +562,8 @@
     public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument";
     /** {@hide} */
     public static final String METHOD_COPY_DOCUMENT = "android:copyDocument";
+    /** {@hide} */
+    public static final String METHOD_MOVE_DOCUMENT = "android:moveDocument";
 
     /** {@hide} */
     public static final String EXTRA_URI = "uri";
@@ -1066,6 +1077,40 @@
     }
 
     /**
+     * Moves the given document under a new parent.
+     *
+     * @param sourceDocumentUri document with {@link Document#FLAG_SUPPORTS_MOVE}
+     * @param targetParentDocumentUri document which will become a new parent of the source
+     *         document.
+     * @return the moved document, or {@code null} if failed.
+     * @hide
+     */
+    public static Uri moveDocument(ContentResolver resolver, Uri sourceDocumentUri,
+            Uri targetParentDocumentUri) {
+        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
+                sourceDocumentUri.getAuthority());
+        try {
+            return moveDocument(client, sourceDocumentUri, targetParentDocumentUri);
+        } catch (Exception e) {
+            Log.w(TAG, "Failed to move document", e);
+            return null;
+        } finally {
+            ContentProviderClient.releaseQuietly(client);
+        }
+    }
+
+    /** {@hide} */
+    public static Uri moveDocument(ContentProviderClient client, Uri sourceDocumentUri,
+            Uri targetParentDocumentUri) throws RemoteException {
+        final Bundle in = new Bundle();
+        in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri);
+        in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri);
+
+        final Bundle out = client.call(METHOD_MOVE_DOCUMENT, null, in);
+        return out.getParcelable(DocumentsContract.EXTRA_URI);
+    }
+
+    /**
      * Open the given image for thumbnail purposes, using any embedded EXIF
      * thumbnail if available, and providing orientation hints from the parent
      * image.
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 87c8fca..ef188a2 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -20,6 +20,7 @@
 import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
 import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
 import static android.provider.DocumentsContract.METHOD_COPY_DOCUMENT;
+import static android.provider.DocumentsContract.METHOD_MOVE_DOCUMENT;
 import static android.provider.DocumentsContract.buildDocumentUri;
 import static android.provider.DocumentsContract.buildDocumentUriMaybeUsingTree;
 import static android.provider.DocumentsContract.buildTreeDocumentUri;
@@ -278,6 +279,24 @@
     }
 
     /**
+     * Move the requested document or a document tree.
+     * <p>
+     * Moves a document including all child documents to another location within
+     * the same document provider. Upon completion returns the document id of
+     * the copied document at the target destination. {@code null} must never
+     * be returned.
+     *
+     * @param sourceDocumentId the document to move.
+     * @param targetParentDocumentId the target document to be a new parent of the
+     *     source document.
+     * @hide
+     */
+    @SuppressWarnings("unused")
+    public String moveDocument(String sourceDocumentId, String targetParentDocumentId)
+            throws FileNotFoundException {
+        throw new UnsupportedOperationException("Move not supported");
+    }
+    /**
      * Return all roots currently provided. To display to users, you must define
      * at least one root. You should avoid making network requests to keep this
      * request fast.
@@ -725,6 +744,32 @@
                     out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
                 }
 
+            } else if (METHOD_MOVE_DOCUMENT.equals(method)) {
+                final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
+                final String targetId = DocumentsContract.getDocumentId(targetUri);
+
+                enforceReadPermissionInner(documentUri, null);
+                enforceWritePermissionInner(targetUri, null);
+
+                final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
+                final String newDocumentId = moveDocument(documentId, targetId);
+
+                if (newDocumentId != null) {
+                    final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
+                            newDocumentId);
+
+                    if (!isTreeUri(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 {
                 throw new UnsupportedOperationException("Method not supported " + method);
             }