Merge "Wire multi-parents support for moving to DocumentsUI."
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 809db31..74fa8d0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -385,6 +385,7 @@
         FileOperations.start(
                 getActivity(),
                 getDisplayState().selectedDocumentsForCopy,
+                getDisplayState().stack.peek(),
                 (DocumentStack) data.getParcelableExtra(Shared.EXTRA_STACK),
                 operationType);
     }
@@ -782,20 +783,21 @@
 
     private void deleteDocuments(final Selection selected) {
 
-            checkArgument(!selected.isEmpty());
-            new GetDocumentsTask() {
-                @Override
-                void onDocumentsReady(List<DocumentInfo> docs) {
-                    // Hide the files in the UI.
-                    final SparseArray<String> hidden = mAdapter.hide(selected.getAll());
+        checkArgument(!selected.isEmpty());
+        final DocumentInfo srcParent = getDisplayState().stack.peek();
+        new GetDocumentsTask() {
+            @Override
+            void onDocumentsReady(List<DocumentInfo> docs) {
+                // Hide the files in the UI.
+                final SparseArray<String> hidden = mAdapter.hide(selected.getAll());
 
-                    checkState(DELETE_JOB_DELAY > DELETE_UNDO_TIMEOUT);
-                    String operationId = FileOperations.delete(
-                            getActivity(), docs, getDisplayState().stack,
-                            DELETE_JOB_DELAY);
-                    showDeleteSnackbar(hidden, operationId);
-                }
-            }.execute(selected);
+                checkState(DELETE_JOB_DELAY > DELETE_UNDO_TIMEOUT);
+                String operationId = FileOperations.delete(
+                        getActivity(), docs, srcParent, getDisplayState().stack,
+                        DELETE_JOB_DELAY);
+                showDeleteSnackbar(hidden, operationId);
+            }
+        }.execute(selected);
     }
 
     private void showDeleteSnackbar(final SparseArray<String> hidden, final String jobId) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
index f3195a7..1d8eba5 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
@@ -199,7 +199,7 @@
                     "Copying " + srcInfo.displayName + " (" + srcInfo.derivedUri + ")"
                     + " to " + dstInfo.displayName + " (" + dstInfo.derivedUri + ")");
 
-            processDocument(srcInfo, dstInfo);
+            processDocument(srcInfo, null, dstInfo);
         }
     }
 
@@ -220,11 +220,15 @@
      * Copies a the given document to the given location.
      *
      * @param src DocumentInfos for the documents to copy.
+     * @param srcParent DocumentInfo for the parent of the document to process.
      * @param dstDirInfo The destination directory.
      * @return True on success, false on failure.
      * @throws RemoteException
+     *
+     * TODO: Stop passing srcParent, as it's not used for copy, but for move only.
      */
-    boolean processDocument(DocumentInfo src, DocumentInfo dstDirInfo) throws RemoteException {
+    boolean processDocument(DocumentInfo src, DocumentInfo srcParent,
+            DocumentInfo dstDirInfo) throws RemoteException {
 
         // TODO: When optimized copy kicks in, we'll not making any progress updates.
         // For now. Local storage isn't using optimized copy.
@@ -332,7 +336,7 @@
             cursor = getClient(srcDir).query(queryUri, queryColumns, null, null, null);
             while (cursor.moveToNext() && !isCanceled()) {
                 DocumentInfo src = DocumentInfo.fromCursor(cursor, srcDir.authority);
-                success &= processDocument(src, destDir);
+                success &= processDocument(src, srcDir, destDir);
             }
         } finally {
             IoUtils.closeQuietly(cursor);
@@ -509,7 +513,7 @@
                 .append("CopyJob")
                 .append("{")
                 .append("id=" + id)
-                .append("srcs=" + mSrcs)
+                .append(", srcs=" + mSrcs)
                 .append(", destination=" + stack)
                 .append("}")
                 .toString();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
index 6a2a794..eef696a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
@@ -35,6 +35,7 @@
 
     private static final String TAG = "DeleteJob";
     private List<DocumentInfo> mSrcs;
+    final DocumentInfo mSrcParent;
 
     /**
      * Moves files to a destination identified by {@code destination}.
@@ -43,12 +44,14 @@
      *
      * @see @link {@link Job} constructor for most param descriptions.
      *
-     * @param srcs List of files to delete
+     * @param srcs List of files to delete.
+     * @param srcParent Parent of all source files.
      */
     DeleteJob(Context service, Context appContext, Listener listener,
-            String id, DocumentStack stack, List<DocumentInfo> srcs) {
+            String id, DocumentStack stack, List<DocumentInfo> srcs, DocumentInfo srcParent) {
         super(service, appContext, listener, OPERATION_DELETE, id, stack);
         this.mSrcs = srcs;
+        this.mSrcParent = srcParent;
     }
 
     @Override
@@ -75,6 +78,8 @@
     void start() throws RemoteException {
         for (DocumentInfo doc : mSrcs) {
             if (DEBUG) Log.d(TAG, "Deleting document @ " + doc.derivedUri);
+            // TODO: Start using mSrcParent as soon as DocumentsProvider::removeDocument() is
+            // implemented.
             if (!deleteDocument(doc)) {
                 Log.w(TAG, "Failed to delete document @ " + doc.derivedUri);
                 onFileFailed(doc);
@@ -88,7 +93,8 @@
                 .append("DeleteJob")
                 .append("{")
                 .append("id=" + id)
-                .append("srcs=" + mSrcs)
+                .append(", srcs=" + mSrcs)
+                .append(", srcParent=" + mSrcParent)
                 .append(", location=" + stack)
                 .append("}")
                 .toString();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
index aca2d7a..5d74cdc 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperationService.java
@@ -65,6 +65,11 @@
     public static final String EXTRA_SRC_LIST = "com.android.documentsui.SRC_LIST";
     public static final String EXTRA_FAILURE = "com.android.documentsui.FAILURE";
 
+    // This extra is used only for moving and deleting. Currently it's not the case,
+    // but in the future those files may be from multiple different parents. In
+    // such case, this needs to be replaced with pairs of parent and child.
+    public static final String EXTRA_SRC_PARENT = "com.android.documentsui.SRC_PARENT";
+
     public static final int OPERATION_UNKNOWN = -1;
     public static final int OPERATION_COPY = 1;
     public static final int OPERATION_MOVE = 2;
@@ -152,14 +157,14 @@
         Job job = null;
         synchronized (mRunning) {
             if (mWakeLock == null) {
-                mWakeLock = mPowerManager.newWakeLock(
-                        PowerManager.PARTIAL_WAKE_LOCK, TAG);
+                mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
             }
 
             List<DocumentInfo> srcs = intent.getParcelableArrayListExtra(EXTRA_SRC_LIST);
+            DocumentInfo srcParent = intent.getParcelableExtra(EXTRA_SRC_PARENT);
             DocumentStack stack = intent.getParcelableExtra(Shared.EXTRA_STACK);
 
-            job = createJob(operationType, jobId, srcs, stack);
+            job = createJob(operationType, jobId, srcs, srcParent, stack);
 
             if (job == null) {
                 return;
@@ -222,7 +227,8 @@
      */
     @GuardedBy("mRunning")
     private @Nullable Job createJob(
-            @OpType int operationType, String id, List<DocumentInfo> srcs, DocumentStack stack) {
+            @OpType int operationType, String id, List<DocumentInfo> srcs, DocumentInfo srcParent,
+            DocumentStack stack) {
 
         if (mRunning.containsKey(id)) {
             Log.w(TAG, "Duplicate job id: " + id
@@ -236,10 +242,12 @@
                 job = jobFactory.createCopy(this, getApplicationContext(), this, id, stack, srcs);
                 break;
             case OPERATION_MOVE:
-                job = jobFactory.createMove(this, getApplicationContext(), this, id, stack, srcs);
+                job = jobFactory.createMove(this, getApplicationContext(), this, id, stack, srcs,
+                        srcParent);
                 break;
             case OPERATION_DELETE:
-                job = jobFactory.createDelete(this, getApplicationContext(), this, id, stack, srcs);
+                job = jobFactory.createDelete(this, getApplicationContext(), this, id, stack, srcs,
+                        srcParent);
                 break;
             default:
                 throw new UnsupportedOperationException();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperations.java b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperations.java
index f59a32a..9d017ee 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/FileOperations.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/FileOperations.java
@@ -26,6 +26,7 @@
 import static com.android.documentsui.services.FileOperationService.EXTRA_JOB_ID;
 import static com.android.documentsui.services.FileOperationService.EXTRA_OPERATION;
 import static com.android.documentsui.services.FileOperationService.EXTRA_SRC_LIST;
+import static com.android.documentsui.services.FileOperationService.EXTRA_SRC_PARENT;
 import static com.android.documentsui.services.FileOperationService.OPERATION_COPY;
 import static com.android.documentsui.services.FileOperationService.OPERATION_DELETE;
 import static com.android.documentsui.services.FileOperationService.OPERATION_MOVE;
@@ -35,6 +36,7 @@
 import android.content.Intent;
 import android.content.res.Resources;
 import android.os.Parcelable;
+import android.support.annotation.VisibleForTesting;
 import android.support.design.widget.Snackbar;
 import android.util.Log;
 
@@ -65,8 +67,8 @@
      * Tries to start the activity. Returns the job id.
      */
     public static String start(
-            Activity activity, List<DocumentInfo> srcDocs, DocumentStack stack,
-            int operationType) {
+            Activity activity, List<DocumentInfo> srcDocs,
+            DocumentStack stack, int operationType) {
 
         if (DEBUG) Log.d(TAG, "Handling generic 'start' call.");
 
@@ -74,7 +76,7 @@
             case OPERATION_COPY:
                 return FileOperations.copy(activity, srcDocs, stack);
             case OPERATION_MOVE:
-                return FileOperations.move(activity, srcDocs, stack);
+                throw new IllegalArgumentException("Moving requires providing the source parent.");
             case OPERATION_DELETE:
                 throw new UnsupportedOperationException("Delete isn't currently supported.");
             default:
@@ -83,14 +85,27 @@
     }
 
     /**
-     * Makes a best effort to cancel operation identified by jobId.
-     *
-     * @param context Context for the intent.
-     * @param jobId The id of the job to cancel.
-     *     Use {@link #createJobId} if you don't have one handy.
-     * @param srcDocs A list of src files to copy.
-     * @param dstStack The copy destination stack.
+     * Tries to start the activity. Returns the job id.
      */
+    public static String start(
+            Activity activity, List<DocumentInfo> srcDocs, DocumentInfo srcParent,
+            DocumentStack stack, int operationType) {
+
+        if (DEBUG) Log.d(TAG, "Handling generic 'start' call.");
+
+        switch (operationType) {
+            case OPERATION_COPY:
+                return FileOperations.copy(activity, srcDocs, stack);
+            case OPERATION_MOVE:
+                return FileOperations.move(activity, srcDocs, srcParent, stack);
+            case OPERATION_DELETE:
+                throw new UnsupportedOperationException("Delete isn't currently supported.");
+            default:
+                throw new UnsupportedOperationException("Unknown operation: " + operationType);
+        }
+    }
+
+    @VisibleForTesting
     public static void cancel(Activity activity, String jobId) {
         if (DEBUG) Log.d(TAG, "Attempting to canceling operation: " + jobId);
 
@@ -101,15 +116,7 @@
         activity.startService(intent);
     }
 
-    /**
-     * Starts the service for a copy operation.
-     *
-     * @param context Context for the intent.
-     * @param jobId A unique jobid for this job.
-     *     Use {@link #createJobId} if you don't have one handy.
-     * @param srcDocs A list of src files to copy.
-     * @param destination The copy destination stack.
-     */
+    @VisibleForTesting
     public static String copy(
             Activity activity, List<DocumentInfo> srcDocs, DocumentStack destination) {
         String jobId = createJobId();
@@ -131,14 +138,17 @@
      * @param jobId A unique jobid for this job.
      *     Use {@link #createJobId} if you don't have one handy.
      * @param srcDocs A list of src files to copy.
+     * @param srcParent Parent of all the source documents.
      * @param destination The move destination stack.
      */
     public static String move(
-            Activity activity, List<DocumentInfo> srcDocs, DocumentStack destination) {
+            Activity activity, List<DocumentInfo> srcDocs, DocumentInfo srcParent,
+            DocumentStack destination) {
         String jobId = createJobId();
         if (DEBUG) Log.d(TAG, "Initiating 'move' operation id: " + jobId);
 
-        Intent intent = createBaseIntent(OPERATION_MOVE, activity, jobId, srcDocs, destination);
+        Intent intent = createBaseIntent(OPERATION_MOVE, activity, jobId, srcDocs, srcParent,
+                destination);
 
         createSharedSnackBar(activity, R.plurals.move_begin, srcDocs.size())
                 .show();
@@ -154,16 +164,19 @@
      * @param jobId A unique jobid for this job.
      *     Use {@link #createJobId} if you don't have one handy.
      * @param srcDocs A list of src files to copy.
+     * @param srcParent Parent of all the source documents.
      * @param delay Number of milliseconds to wait before executing the job.
      * @return Id of the job.
      */
     public static String delete(
-            Activity activity, List<DocumentInfo> srcDocs, DocumentStack location, int delay) {
+            Activity activity, List<DocumentInfo> srcDocs, DocumentInfo srcParent,
+            DocumentStack location, int delay) {
         String jobId = createJobId();
         if (DEBUG) Log.d(TAG, "Initiating 'delete' operation id " + jobId
                 + " delayed by " + delay + " milliseconds.");
 
-        Intent intent = createBaseIntent(OPERATION_DELETE, activity, jobId, srcDocs, location);
+        Intent intent = createBaseIntent(OPERATION_DELETE, activity, jobId, srcDocs, srcParent,
+                location);
         intent.putExtra(EXTRA_DELAY, delay);
         activity.startService(intent);
 
@@ -171,7 +184,7 @@
     }
 
     /**
-     * Starts the service for a move operation.
+     * Starts the service for an operation.
      *
      * @param jobId A unique jobid for this job.
      *     Use {@link #createJobId} if you don't have one handy.
@@ -179,13 +192,35 @@
      * @return Id of the job.
      */
     public static Intent createBaseIntent(
-            @OpType int operationType, Context context, String jobId,
-            List<DocumentInfo> srcDocs, DocumentStack localeStack) {
+            @OpType int operationType, Context context, String jobId, List<DocumentInfo> srcDocs,
+            DocumentStack localeStack) {
 
         Intent intent = new Intent(context, FileOperationService.class);
         intent.putExtra(EXTRA_JOB_ID, jobId);
-        intent.putParcelableArrayListExtra(
-                EXTRA_SRC_LIST, asArrayList(srcDocs));
+        intent.putParcelableArrayListExtra(EXTRA_SRC_LIST, asArrayList(srcDocs));
+        intent.putExtra(EXTRA_STACK, (Parcelable) localeStack);
+        intent.putExtra(EXTRA_OPERATION, operationType);
+
+        return intent;
+    }
+
+    /**
+     * Starts the service for an operation.
+     *
+     * @param jobId A unique jobid for this job.
+     *     Use {@link #createJobId} if you don't have one handy.
+     * @param srcDocs A list of src files to copy.
+     * @param srcParent Parent of all the source documents.
+     * @return Id of the job.
+     */
+    public static Intent createBaseIntent(
+            @OpType int operationType, Context context, String jobId,
+            List<DocumentInfo> srcDocs, DocumentInfo srcParent, DocumentStack localeStack) {
+
+        Intent intent = new Intent(context, FileOperationService.class);
+        intent.putExtra(EXTRA_JOB_ID, jobId);
+        intent.putParcelableArrayListExtra(EXTRA_SRC_LIST, asArrayList(srcDocs));
+        intent.putExtra(EXTRA_SRC_PARENT, srcParent);
         intent.putExtra(EXTRA_STACK, (Parcelable) localeStack);
         intent.putExtra(EXTRA_OPERATION, operationType);
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/Job.java b/packages/DocumentsUI/src/com/android/documentsui/services/Job.java
index f351df9..9534d6c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/Job.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/Job.java
@@ -274,13 +274,15 @@
         }
 
         Job createMove(Context service, Context appContext, Listener listener,
-                String id, DocumentStack stack, List<DocumentInfo> srcs) {
-            return new MoveJob(service, appContext, listener, id, stack, srcs);
+                String id, DocumentStack stack, List<DocumentInfo> srcs,
+                DocumentInfo srcParent) {
+            return new MoveJob(service, appContext, listener, id, stack, srcs, srcParent);
         }
 
         Job createDelete(Context service, Context appContext, Listener listener,
-                String id, DocumentStack stack, List<DocumentInfo> srcs) {
-            return new DeleteJob(service, appContext, listener, id, stack, srcs);
+                String id, DocumentStack stack, List<DocumentInfo> srcs,
+                DocumentInfo srcParent) {
+            return new DeleteJob(service, appContext, listener, id, stack, srcs, srcParent);
         }
     }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
index 2a0262c..9b72077 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
@@ -33,9 +33,11 @@
 
 import java.util.List;
 
+// TODO: Stop extending CopyJob.
 final class MoveJob extends CopyJob {
 
     private static final String TAG = "MoveJob";
+    final DocumentInfo mSrcParent;
 
     /**
      * Moves files to a destination identified by {@code destination}.
@@ -45,10 +47,12 @@
      * @see @link {@link Job} constructor for most param descriptions.
      *
      * @param srcs List of files to be moved.
+     * @param srcParent Parent of all source files.
      */
     MoveJob(Context service, Context appContext, Listener listener,
-            String id, DocumentStack destination, List<DocumentInfo> srcs) {
+            String id, DocumentStack destination, List<DocumentInfo> srcs, DocumentInfo srcParent) {
         super(service, appContext, listener, OPERATION_MOVE, id, destination, srcs);
+        this.mSrcParent = srcParent;
     }
 
     @Override
@@ -76,8 +80,8 @@
                 R.plurals.move_error_notification_title, R.drawable.ic_menu_copy);
     }
 
-    @Override
-    boolean processDocument(DocumentInfo src, DocumentInfo dest) throws RemoteException {
+    boolean processDocument(DocumentInfo src, DocumentInfo srcParent, DocumentInfo dest)
+            throws RemoteException {
 
         // TODO: When optimized move kicks in, we're not making any progress updates. FIX IT!
 
@@ -86,7 +90,8 @@
         if (src.authority.equals(dest.authority)) {
             if ((src.flags & Document.FLAG_SUPPORTS_MOVE) != 0) {
                 if (DocumentsContract.moveDocument(getClient(src), src.derivedUri,
-                        Uri.EMPTY /* Not used yet */, dest.derivedUri) == null) {
+                        srcParent != null ? srcParent.derivedUri : mSrcParent.derivedUri,
+                        dest.derivedUri) == null) {
                     onFileFailed(src);
                     return false;
                 }
@@ -106,6 +111,7 @@
         // If we couldn't do an optimized copy...we fall back to vanilla byte copy.
         boolean copied = byteCopyDocument(src, dest);
 
+        // TODO: Replace deleteDocument() with removeDocument() once implemented.
         return copied && !isCanceled() && deleteDocument(src);
     }
 
@@ -115,7 +121,8 @@
                 .append("MoveJob")
                 .append("{")
                 .append("id=" + id)
-                .append("srcs=" + mSrcs)
+                .append(", srcs=" + mSrcs)
+                .append(", srcParent=" + mSrcParent)
                 .append(", destination=" + stack)
                 .append("}")
                 .toString();
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractCopyJobTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractCopyJobTest.java
index ec21150..eb8a061 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractCopyJobTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractCopyJobTest.java
@@ -23,6 +23,7 @@
 import android.test.suitebuilder.annotation.MediumTest;
 
 import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.DocumentStack;
 
 import java.util.List;
 
@@ -109,7 +110,9 @@
     public void runNoCopyDirToSelfTest() throws Exception {
         Uri testDir = mDocs.createFolder(mSrcRoot, "someDir");
 
-        createJob(newArrayList(testDir), testDir).run();
+        createJob(newArrayList(testDir),
+                DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId),
+                testDir).run();
 
         mJobListener.waitForFinished();
         mJobListener.assertFailed();
@@ -122,7 +125,9 @@
         Uri testDir = mDocs.createFolder(mSrcRoot, "someDir");
         Uri destDir = mDocs.createFolder(testDir, "theDescendent");
 
-        createJob(newArrayList(testDir), destDir).run();
+        createJob(newArrayList(testDir),
+                DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId),
+                destDir).run();
 
         mJobListener.waitForFinished();
         mJobListener.assertFailed();
@@ -148,10 +153,13 @@
     }
 
     /**
-     * Creates a job with a stack consisting to the default destination.
+     * Creates a job with a stack consisting to the default source and destination.
+     * TODO: Clean up, as mDestRoot.documentInfo may not really be the parent of
+     * srcs.
      */
     final T createJob(List<Uri> srcs) throws Exception {
+        Uri srcParent = DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId);
         Uri destination = DocumentsContract.buildDocumentUri(AUTHORITY, mDestRoot.documentId);
-        return createJob(srcs, destination);
+        return createJob(srcs, srcParent, destination);
     }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractJobTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractJobTest.java
index 691af6a..e559503 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractJobTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/AbstractJobTest.java
@@ -85,7 +85,7 @@
         mDestRoot = mDocs.getRoot(ROOT_1_ID);
     }
 
-    final T createJob(List<Uri> srcs, Uri destination) throws Exception {
+    final T createJob(List<Uri> srcs, Uri srcParent, Uri destination) throws Exception {
         DocumentStack stack = new DocumentStack();
         stack.push(DocumentInfo.fromUri(mResolver, destination));
 
@@ -94,8 +94,9 @@
             srcDocs.add(DocumentInfo.fromUri(mResolver, src));
         }
 
-        return createJob(srcDocs, stack);
+        return createJob(srcDocs, DocumentInfo.fromUri(mResolver, srcParent), stack);
     }
 
-    abstract T createJob(List<DocumentInfo> srcs, DocumentStack destination) throws Exception;
+    abstract T createJob(List<DocumentInfo> srcs, DocumentInfo srcParent, DocumentStack destination)
+            throws Exception;
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/CopyJobTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/CopyJobTest.java
index 1acf2dc..543396e 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/CopyJobTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/CopyJobTest.java
@@ -59,7 +59,9 @@
     }
 
     @Override
-    CopyJob createJob(List<DocumentInfo> srcs, DocumentStack stack) throws Exception {
+    // TODO: Stop passing srcParent here, as it's not used for copying.
+    CopyJob createJob(List<DocumentInfo> srcs, DocumentInfo srcParent, DocumentStack stack)
+            throws Exception {
         return new CopyJob(
                 mContext, mContext, mJobListener, FileOperations.createJobId(), stack, srcs);
     }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/DeleteJobTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/DeleteJobTest.java
index d6d10239..722df75 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/DeleteJobTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/DeleteJobTest.java
@@ -37,7 +37,8 @@
         Uri testFile2 = mDocs.createDocument(mSrcRoot, "text/plain", "test2.txt");
         mDocs.writeDocument(testFile2, FRUITY_BYTES);
 
-        createJob(newArrayList(testFile1, testFile2)).run();
+        createJob(newArrayList(testFile1, testFile2),
+                DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId)).run();
         mJobListener.waitForFinished();
 
         mDocs.assertChildCount(mSrcRoot, 0);
@@ -46,14 +47,17 @@
     /**
      * Creates a job with a stack consisting to the default src directory.
      */
-    private final DeleteJob createJob(List<Uri> srcs) throws Exception {
+    private final DeleteJob createJob(List<Uri> srcs, Uri srcParent) throws Exception {
         Uri stack = DocumentsContract.buildDocumentUri(AUTHORITY, mSrcRoot.documentId);
-        return createJob(srcs, stack);
+        return createJob(srcs, srcParent, stack);
     }
 
     @Override
-    DeleteJob createJob(List<DocumentInfo> srcs, DocumentStack stack) throws Exception {
+    // TODO: Remove inheritance, as stack is not used for deleting, nor srcParent.
+    DeleteJob createJob(List<DocumentInfo> srcs, DocumentInfo srcParent, DocumentStack stack)
+            throws Exception {
         return new DeleteJob(
-                mContext, mContext, mJobListener, FileOperations.createJobId(), stack, srcs);
+                mContext, mContext, mJobListener, FileOperations.createJobId(), stack, srcs,
+                srcParent);
     }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java
index 69d2db7..749264a 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/services/MoveJobTest.java
@@ -19,6 +19,8 @@
 import static com.google.common.collect.Lists.newArrayList;
 
 import android.net.Uri;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract;
 import android.test.suitebuilder.annotation.MediumTest;
 
 import com.android.documentsui.model.DocumentInfo;
@@ -36,8 +38,9 @@
     }
 
     public void testMoveVirtualTypedFile() throws Exception {
+        mDocs.createFolder(mSrcRoot, "hello");
         Uri testFile = mDocs.createVirtualFile(
-                mSrcRoot, "/virtual.sth", "virtual/mime-type",
+                mSrcRoot, "/hello/virtual.sth", "virtual/mime-type",
                 FRUITY_BYTES, "application/pdf", "text/html");
         createJob(newArrayList(testFile)).run();
 
@@ -89,9 +92,13 @@
         mDocs.assertChildCount(mSrcRoot, 1);
     }
 
+    // TODO: Add test cases for moving when multi-parented.
+
     @Override
-    MoveJob createJob(List<DocumentInfo> srcs, DocumentStack stack) throws Exception {
+    MoveJob createJob(List<DocumentInfo> srcs, DocumentInfo srcParent, DocumentStack stack)
+            throws Exception {
         return new MoveJob(
-                mContext, mContext, mJobListener, FileOperations.createJobId(), stack, srcs);
+                mContext, mContext, mJobListener, FileOperations.createJobId(), stack, srcs,
+                srcParent);
     }
 }