Add tests for copying virtual typed files.

Bug: 26147488
Change-Id: I44cfae518f4b6dfceed5b9f50b132e2acf161b3f
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index c6c1961..6a5911b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -501,16 +501,23 @@
 
         // If the file is virtual, but can be converted to another format, then try to copy it
         // as such format. Also, append an extension for the target mime type (if known).
-        if (srcInfo.isVirtualDocument() && srcInfo.isTypedDocument()) {
+        if (srcInfo.isVirtualDocument()) {
+            if (!srcInfo.isTypedDocument()) {
+                // Impossible to copy a file which is virtual, but not typed.
+                mFailedFiles.add(srcInfo);
+                return false;
+            }
             final String[] streamTypes = getContentResolver().getStreamTypes(
                     srcInfo.derivedUri, "*/*");
-            if (streamTypes.length > 0) {
+            if (streamTypes != null && streamTypes.length > 0) {
                 dstMimeType = streamTypes[0];
                 final String extension = MimeTypeMap.getSingleton().
                         getExtensionFromMimeType(dstMimeType);
                 dstDisplayName = srcInfo.displayName +
                         (extension != null ? "." + extension : srcInfo.displayName);
             } else {
+                // The provider says that it supports typed documents, but doesn't say
+                // anything about available formats.
                 // TODO: Log failures. b/26192412
                 mFailedFiles.add(srcInfo);
                 return false;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java
index 079d599..24a8113 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/CopyServiceTest.java
@@ -97,7 +97,7 @@
 
     public void testCopyFile() throws Exception {
         String srcPath = "/test0.txt";
-        Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
+        Uri testFile = mStorage.createRegularFile(SRC_ROOT, srcPath, "text/plain",
                 "The five boxing wizards jump quickly".getBytes());
 
         startService(createCopyIntent(Lists.newArrayList(testFile)));
@@ -110,10 +110,33 @@
         assertCopied(srcPath);
     }
 
+    public void testCopyVirtualTypedFile() throws Exception {
+        String srcPath = "/virtual.sth";
+        String expectedDstPath = "/virtual.sth.pdf";
+        ArrayList<String> streamTypes = new ArrayList<>();
+        streamTypes.add("application/pdf");
+        streamTypes.add("text/html");
+        String testContent = "I love fruit cakes!";
+        Uri testFile = mStorage.createVirtualFile(SRC_ROOT, srcPath, "virtual/mime-type",
+                streamTypes, testContent.getBytes());
+
+        startService(createCopyIntent(Lists.newArrayList(testFile)));
+
+        // 2 operations: file creation, then writing data.
+        mResolver.waitForChanges(2);
+
+        // Verify that one file was copied.
+        assertDestFileCount(1);
+
+        byte[] dstContent = readFile(DST_ROOT, expectedDstPath);
+        MoreAsserts.assertEquals("Moved file contents differ", testContent.getBytes(), dstContent);
+    }
+
     public void testMoveFile() throws Exception {
         String srcPath = "/test0.txt";
         String testContent = "The five boxing wizards jump quickly";
-        Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain", testContent.getBytes());
+        Uri testFile = mStorage.createRegularFile(SRC_ROOT, srcPath, "text/plain",
+                testContent.getBytes());
 
         Intent moveIntent = createCopyIntent(Lists.newArrayList(testFile));
         moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
@@ -142,9 +165,12 @@
                 "/test2.txt"
         };
         List<Uri> testFiles = Lists.newArrayList(
-                mStorage.createFile(SRC_ROOT, srcPaths[0], "text/plain", testContent[0].getBytes()),
-                mStorage.createFile(SRC_ROOT, srcPaths[1], "text/plain", testContent[1].getBytes()),
-                mStorage.createFile(SRC_ROOT, srcPaths[2], "text/plain", testContent[2].getBytes()));
+                mStorage.createRegularFile(SRC_ROOT, srcPaths[0], "text/plain",
+                        testContent[0].getBytes()),
+                mStorage.createRegularFile(SRC_ROOT, srcPaths[1], "text/plain",
+                        testContent[1].getBytes()),
+                mStorage.createRegularFile(SRC_ROOT, srcPaths[2], "text/plain",
+                        testContent[2].getBytes()));
 
         // Copy all the test files.
         startService(createCopyIntent(testFiles));
@@ -195,7 +221,6 @@
 
         Intent intent = createCopyIntent(Lists.newArrayList(testDir), descDir);
         startService(intent);
-
         getService().addFinishedListener(mListener);
 
         mListener.waitForFinished();
@@ -240,9 +265,9 @@
         };
         // Create test dir; put some files in it.
         Uri testDir = createTestDirectory(srcDir);
-        mStorage.createFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes());
-        mStorage.createFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes());
-        mStorage.createFile(SRC_ROOT, srcFiles[2], "text/plain", testContent[2].getBytes());
+        mStorage.createRegularFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes());
+        mStorage.createRegularFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes());
+        mStorage.createRegularFile(SRC_ROOT, srcFiles[2], "text/plain", testContent[2].getBytes());
 
         Intent moveIntent = createCopyIntent(Lists.newArrayList(testDir));
         moveIntent.putExtra(CopyService.EXTRA_TRANSFER_MODE, CopyService.TRANSFER_MODE_MOVE);
@@ -270,7 +295,7 @@
 
     public void testCopyFileWithReadErrors() throws Exception {
         String srcPath = "/test0.txt";
-        Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
+        Uri testFile = mStorage.createRegularFile(SRC_ROOT, srcPath, "text/plain",
                 "The five boxing wizards jump quickly".getBytes());
 
         mStorage.simulateReadErrorsForFile(testFile);
@@ -284,9 +309,26 @@
         assertDestFileCount(0);
     }
 
+    public void testCopyVirtualNonTypedFile() throws Exception {
+        String srcPath = "/non-typed.sth";
+        // Empty stream types causes the FLAG_SUPPORTS_TYPED_DOCUMENT to be not set.
+        ArrayList<String> streamTypes = new ArrayList<>();
+        Uri testFile = mStorage.createVirtualFile(SRC_ROOT, srcPath, "virtual/mime-type",
+                streamTypes, "I love Tokyo!".getBytes());
+
+        Intent intent = createCopyIntent(Lists.newArrayList(testFile));
+        startService(intent);
+        getService().addFinishedListener(mListener);
+
+        mListener.waitForFinished();
+        mListener.assertFailedCount(1);
+        mListener.assertFileFailed("non-typed.sth");
+        assertDestFileCount(0);
+    }
+
     public void testMoveFileWithReadErrors() throws Exception {
         String srcPath = "/test0.txt";
-        Uri testFile = mStorage.createFile(SRC_ROOT, srcPath, "text/plain",
+        Uri testFile = mStorage.createRegularFile(SRC_ROOT, srcPath, "text/plain",
                 "The five boxing wizards jump quickly".getBytes());
 
         mStorage.simulateReadErrorsForFile(testFile);
@@ -326,10 +368,10 @@
         };
         // Create test dir; put some files in it.
         Uri testDir = createTestDirectory(srcDir);
-        mStorage.createFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes());
+        mStorage.createRegularFile(SRC_ROOT, srcFiles[0], "text/plain", testContent[0].getBytes());
         Uri errFile = mStorage
-                .createFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes());
-        mStorage.createFile(SRC_ROOT, srcFiles[2], "text/plain", testContent[2].getBytes());
+                .createRegularFile(SRC_ROOT, srcFiles[1], "text/plain", testContent[1].getBytes());
+        mStorage.createRegularFile(SRC_ROOT, srcFiles[2], "text/plain", testContent[2].getBytes());
 
         mStorage.simulateReadErrorsForFile(errFile);
 
@@ -363,7 +405,7 @@
     }
 
     private Uri createTestDirectory(String dir) throws IOException {
-        return mStorage.createFile(
+        return mStorage.createRegularFile(
                 SRC_ROOT, dir, DocumentsContract.Document.MIME_TYPE_DIR, null);
     }
 
@@ -473,6 +515,7 @@
 
         final CountDownLatch latch = new CountDownLatch(1);
         final List<DocumentInfo> failedDocs = new ArrayList<>();
+
         @Override
         public void onFinished(List<DocumentInfo> failed) {
             failedDocs.addAll(failed);
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
index bcf946e..63a519f 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
@@ -324,11 +324,11 @@
         }
         for (final String mimeType : document.streamTypes) {
             // Strict compare won't accept wildcards, but that's OK for tests, as DocumentsUI
-            // doesn't use them for openTypedDocument.
+            // doesn't use them for getStreamTypes nor openTypedDocument.
             if (mimeType.equals(mimeTypeFilter)) {
                 ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
                             document.file, ParcelFileDescriptor.MODE_READ_ONLY);
-                if (!documentId.equals(mSimulateReadErrors)) {
+                if (documentId.equals(mSimulateReadErrors)) {
                     pfd = new ParcelFileDescriptor(pfd) {
                         @Override
                         public void checkError() throws IOException {
@@ -342,6 +342,23 @@
         throw new IllegalArgumentException("Invalid MIME type filter for openTypedDocument().");
     }
 
+    @Override
+    public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
+        final StubDocument document = mStorage.get(DocumentsContract.getDocumentId(uri));
+        if (document == null) {
+            throw new IllegalArgumentException(
+                    "The provided Uri is incorrect, or the file is gone.");
+        }
+        if ((document.flags & Document.FLAG_SUPPORTS_TYPED_DOCUMENT) == 0) {
+            return null;
+        }
+        if (!"*/*".equals(mimeTypeFilter)) {
+            // Not used by DocumentsUI, so don't bother implementing it.
+            throw new UnsupportedOperationException();
+        }
+        return document.streamTypes.toArray(new String[document.streamTypes.size()]);
+    }
+
     private ParcelFileDescriptor startWrite(final StubDocument document)
             throws FileNotFoundException {
         ParcelFileDescriptor[] pipe;
@@ -476,23 +493,14 @@
     }
 
     @VisibleForTesting
-    public Uri createFile(String rootId, String path, String mimeType, byte[] content)
+    public File createFile(String rootId, String path, String mimeType, byte[] content)
             throws FileNotFoundException, IOException {
         Log.d(TAG, "Creating test file " + rootId + ":" + path);
         StubDocument root = mRoots.get(rootId).document;
         if (root == null) {
             throw new FileNotFoundException("No roots with the ID " + rootId + " were found");
         }
-        File file = new File(root.file, path.substring(1));
-        StubDocument parent = mStorage.get(getDocumentIdForFile(file.getParentFile()));
-        if (parent == null) {
-            parent = mStorage.get(createFile(rootId, file.getParentFile().getPath(),
-                    DocumentsContract.Document.MIME_TYPE_DIR, null));
-            Log.d(TAG, "Created parent " + parent.documentId);
-        } else {
-            Log.d(TAG, "Found parent " + parent.documentId);
-        }
-
+        final File file = new File(root.file, path.substring(1));
         if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) {
             if (!file.mkdirs()) {
                 throw new FileNotFoundException("Couldn't create directory " + file.getPath());
@@ -501,10 +509,20 @@
             if (!file.createNewFile()) {
                 throw new FileNotFoundException("Couldn't create file " + file.getPath());
             }
-            // Add content to the file.
-            FileOutputStream fout = new FileOutputStream(file);
-            fout.write(content);
-            fout.close();
+            try (final FileOutputStream fout = new FileOutputStream(file)) {
+                fout.write(content);
+            }
+        }
+        return file;
+    }
+
+    @VisibleForTesting
+    public Uri createRegularFile(String rootId, String path, String mimeType, byte[] content)
+            throws FileNotFoundException, IOException {
+        final File file = createFile(rootId, path, mimeType, content);
+        final StubDocument parent = mStorage.get(getDocumentIdForFile(file.getParentFile()));
+        if (parent == null) {
+            throw new FileNotFoundException("Parent not found.");
         }
         final StubDocument document = StubDocument.createRegularDocument(file, mimeType, parent);
         mStorage.put(document.documentId, document);
@@ -512,6 +530,21 @@
     }
 
     @VisibleForTesting
+    public Uri createVirtualFile(
+            String rootId, String path, String mimeType, List<String> streamTypes, byte[] content)
+            throws FileNotFoundException, IOException {
+        final File file = createFile(rootId, path, mimeType, content);
+        final StubDocument parent = mStorage.get(getDocumentIdForFile(file.getParentFile()));
+        if (parent == null) {
+            throw new FileNotFoundException("Parent not found.");
+        }
+        final StubDocument document = StubDocument.createVirtualDocument(
+                file, mimeType, streamTypes, parent);
+        mStorage.put(document.documentId, document);
+        return DocumentsContract.buildDocumentUri(mAuthority,  document.documentId);
+    }
+
+    @VisibleForTesting
     public File getFile(String rootId, String path) throws FileNotFoundException {
         StubDocument root = mRoots.get(rootId).document;
         if (root == null) {