Merge "Add findPath method to MtpDocumentsProvider."
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index 1823711..96f91d9 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -35,23 +35,21 @@
 import android.os.ParcelFileDescriptor;
 import android.os.storage.StorageManager;
 import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Path;
 import android.provider.DocumentsContract.Root;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsProvider;
 import android.provider.Settings;
 import android.system.ErrnoException;
-import android.system.Os;
-import android.system.OsConstants;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.io.File;
-import java.io.FileDescriptor;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeoutException;
@@ -414,6 +412,51 @@
         }
     }
 
+    @Override
+    public Path findPath(String childDocumentId, String parentDocumentId)
+            throws FileNotFoundException {
+        final LinkedList<String> ids = new LinkedList<>();
+        final Identifier childIdentifier = mDatabase.createIdentifier(childDocumentId);
+
+        Identifier i = childIdentifier;
+        outer: while (true) {
+            if (i.mDocumentId.equals(parentDocumentId)) {
+                ids.addFirst(i.mDocumentId);
+                break;
+            }
+            switch (i.mDocumentType) {
+                case MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT:
+                    ids.addFirst(i.mDocumentId);
+                    i = mDatabase.getParentIdentifier(i.mDocumentId);
+                    break;
+                case MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE: {
+                    // Check if there is the multiple storage.
+                    final Identifier deviceIdentifier =
+                            mDatabase.getParentIdentifier(i.mDocumentId);
+                    final String[] storageIds =
+                            mDatabase.getStorageDocumentIds(deviceIdentifier.mDocumentId);
+                    // Add storage's document ID to the path only when the device has multiple
+                    // storages.
+                    if (storageIds.length > 1) {
+                        ids.addFirst(i.mDocumentId);
+                        break outer;
+                    }
+                    i = deviceIdentifier;
+                    break;
+                }
+                case MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE:
+                    ids.addFirst(i.mDocumentId);
+                    break outer;
+            }
+        }
+
+        if (parentDocumentId != null) {
+            return new Path(null, ids);
+        } else {
+            return new Path(/* Should be same with root ID */ i.mDocumentId, ids);
+        }
+    }
+
     void openDevice(int deviceId) throws IOException {
         synchronized (mDeviceListLock) {
             if (mDeviceToolkits.containsKey(deviceId)) {
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index 3f7e8b6..a29e27d 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -21,9 +21,9 @@
 import android.mtp.MtpObjectInfo;
 import android.net.Uri;
 import android.os.ParcelFileDescriptor;
-import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
 import android.os.storage.StorageManager;
 import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Path;
 import android.provider.DocumentsContract.Root;
 import android.system.Os;
 import android.system.OsConstants;
@@ -34,6 +34,9 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.Objects;
+import java.util.Queue;
 import java.util.concurrent.TimeoutException;
 
 import static com.android.mtp.MtpDatabase.strings;
@@ -770,6 +773,64 @@
         assertEquals(0x400000000L, cursor.getLong(0));
     }
 
+    public void testFindPath_singleStorage_toRoot() throws Exception {
+        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+        setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
+        setupHierarchyDocuments("1");
+
+        final Path path = mProvider.findPath("15", null);
+        assertEquals("1", path.getRootId());
+        assertEquals(4, path.getPath().size());
+        assertEquals("1", path.getPath().get(0));
+        assertEquals("3", path.getPath().get(1));
+        assertEquals("6", path.getPath().get(2));
+        assertEquals("15", path.getPath().get(3));
+    }
+
+    public void testFindPath_singleStorage_toDoc() throws Exception {
+        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+        setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Storage", 1000, 1000, "") });
+        setupHierarchyDocuments("1");
+
+        final Path path = mProvider.findPath("18", "3");
+        assertNull(path.getRootId());
+        assertEquals(3, path.getPath().size());
+        assertEquals("3", path.getPath().get(0));
+        assertEquals("7", path.getPath().get(1));
+        assertEquals("18", path.getPath().get(2));
+    }
+
+    public void testFindPath_multiStorage_toRoot() throws Exception {
+        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+        setupRoots(0, new MtpRoot[] {
+                new MtpRoot(0, 0, "Storage A", 1000, 1000, ""),
+                new MtpRoot(0, 1, "Storage B", 1000, 1000, "") });
+        setupHierarchyDocuments("2");
+
+        final Path path = mProvider.findPath("16", null);
+        assertEquals("2", path.getRootId());
+        assertEquals(4, path.getPath().size());
+        assertEquals("2", path.getPath().get(0));
+        assertEquals("4", path.getPath().get(1));
+        assertEquals("7", path.getPath().get(2));
+        assertEquals("16", path.getPath().get(3));
+    }
+
+    public void testFindPath_multiStorage_toDoc() throws Exception {
+        setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
+        setupRoots(0, new MtpRoot[] {
+                new MtpRoot(0, 0, "Storage A", 1000, 1000, ""),
+                new MtpRoot(0, 1, "Storage B", 1000, 1000, "") });
+        setupHierarchyDocuments("2");
+
+        final Path path = mProvider.findPath("19", "4");
+        assertNull(path.getRootId());
+        assertEquals(3, path.getPath().size());
+        assertEquals("4", path.getPath().get(0));
+        assertEquals("8", path.getPath().get(1));
+        assertEquals("19", path.getPath().get(2));
+    }
+
     private void setupProvider(int flag) {
         mDatabase = new MtpDatabase(getContext(), flag);
         mProvider = new MtpDocumentsProvider();
@@ -816,11 +877,70 @@
         final int[] handles = new int[objects.length];
         int i = 0;
         for (final MtpObjectInfo info : objects) {
-            handles[i] = info.getObjectHandle();
+            handles[i++] = info.getObjectHandle();
             mMtpManager.setObjectInfo(deviceId, info);
         }
         mMtpManager.setObjectHandles(deviceId, storageId, parentHandle, handles);
         return getStrings(mProvider.queryChildDocuments(
                 parentDocumentId, strings(DocumentsContract.Document.COLUMN_DOCUMENT_ID), null));
     }
+
+    static class HierarchyDocument {
+        int depth;
+        String documentId;
+        int objectHandle;
+        int parentHandle;
+
+        HierarchyDocument createChildDocument(int newHandle) {
+            final HierarchyDocument doc = new HierarchyDocument();
+            doc.depth = depth - 1;
+            doc.objectHandle = newHandle;
+            doc.parentHandle = objectHandle;
+            return doc;
+        }
+
+        MtpObjectInfo toObjectInfo() {
+            return new MtpObjectInfo.Builder()
+                    .setName("doc_" + documentId)
+                    .setFormat(depth > 0 ?
+                            MtpConstants.FORMAT_ASSOCIATION : MtpConstants.FORMAT_TEXT)
+                    .setObjectHandle(objectHandle)
+                    .setParent(parentHandle)
+                    .build();
+        }
+    }
+
+    private void setupHierarchyDocuments(String documentId) throws Exception {
+        final Queue<HierarchyDocument> ids = new LinkedList<>();
+        final HierarchyDocument firstDocument = new HierarchyDocument();
+        firstDocument.depth = 3;
+        firstDocument.documentId = documentId;
+        firstDocument.objectHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
+        ids.add(firstDocument);
+
+        int objectHandle = 100;
+        while (!ids.isEmpty()) {
+            final HierarchyDocument document = ids.remove();
+            final HierarchyDocument[] children = new HierarchyDocument[] {
+                    document.createChildDocument(objectHandle++),
+                    document.createChildDocument(objectHandle++),
+                    document.createChildDocument(objectHandle++),
+            };
+            final String[] childDocIds = setupDocuments(
+                    0, 0, document.objectHandle, document.documentId, new MtpObjectInfo[] {
+                            children[0].toObjectInfo(),
+                            children[1].toObjectInfo(),
+                            children[2].toObjectInfo(),
+                    });
+            children[0].documentId = childDocIds[0];
+            children[1].documentId = childDocIds[1];
+            children[2].documentId = childDocIds[2];
+
+            if (children[0].depth > 0) {
+                ids.add(children[0]);
+                ids.add(children[1]);
+                ids.add(children[2]);
+            }
+        }
+    }
 }