/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.documentsui.archives; import android.database.Cursor; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.provider.DocumentsContract.Document; import android.test.AndroidTestCase; import androidx.test.InstrumentationRegistry; import androidx.test.filters.MediumTest; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; import java.util.Scanner; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @MediumTest public class WriteableArchiveTest extends AndroidTestCase { private static final Uri ARCHIVE_URI = Uri.parse("content://i/love/strawberries"); private static final String NOTIFICATION_URI = "content://com.android.documentsui.archives/notification-uri"; private ExecutorService mExecutor = null; private Archive mArchive = null; private TestUtils mTestUtils = null; private File mFile = null; @Override public void setUp() throws Exception { super.setUp(); mExecutor = Executors.newSingleThreadExecutor(); mTestUtils = new TestUtils(InstrumentationRegistry.getTargetContext(), InstrumentationRegistry.getContext(), mExecutor); mFile = mTestUtils.createTemporaryFile(); mArchive = WriteableArchive.createForParcelFileDescriptor( InstrumentationRegistry.getTargetContext(), ParcelFileDescriptor.open(mFile, ParcelFileDescriptor.MODE_WRITE_ONLY), ARCHIVE_URI, ParcelFileDescriptor.MODE_WRITE_ONLY, Uri.parse(NOTIFICATION_URI)); } @Override public void tearDown() throws Exception { mExecutor.shutdown(); assertTrue(mExecutor.awaitTermination(3 /* timeout */, TimeUnit.SECONDS)); if (mFile != null) { mFile.delete(); } if (mArchive != null) { mArchive.close(); } super.tearDown(); } public static ArchiveId createArchiveId(String path) { return new ArchiveId(ARCHIVE_URI, ParcelFileDescriptor.MODE_WRITE_ONLY, path); } public void testCreateDocument() throws IOException { final String dirDocumentId = mArchive.createDocument(createArchiveId("/").toDocumentId(), Document.MIME_TYPE_DIR, "dir"); assertEquals(createArchiveId("/dir/").toDocumentId(), dirDocumentId); final String documentId = mArchive.createDocument(dirDocumentId, "image/jpeg", "test.jpeg"); assertEquals(createArchiveId("/dir/test.jpeg").toDocumentId(), documentId); try { mArchive.createDocument(dirDocumentId, "image/jpeg", "test.jpeg"); fail("Creating should fail, as the document already exists."); } catch (IllegalStateException e) { // Expected. } try { mArchive.createDocument(createArchiveId("/").toDocumentId(), "image/jpeg", "test.jpeg/"); fail("Creating should fail, as the document name is invalid."); } catch (IllegalStateException e) { // Expected. } try { mArchive.createDocument(createArchiveId("/").toDocumentId(), Document.MIME_TYPE_DIR, "test/"); fail("Creating should fail, as the document name is invalid."); } catch (IllegalStateException e) { // Expected. } try { mArchive.createDocument(createArchiveId("/").toDocumentId(), Document.MIME_TYPE_DIR, ".."); fail("Creating should fail, as the document name is invalid."); } catch (IllegalStateException e) { // Expected. } try { mArchive.createDocument(createArchiveId("/").toDocumentId(), Document.MIME_TYPE_DIR, "."); fail("Creating should fail, as the document name is invalid."); } catch (IllegalStateException e) { // Expected. } try { mArchive.createDocument(createArchiveId("/").toDocumentId(), Document.MIME_TYPE_DIR, ""); fail("Creating should fail, as the document name is invalid."); } catch (IllegalStateException e) { // Expected. } try { mArchive.createDocument(createArchiveId("/").toDocumentId(), "image/jpeg", "a/b.jpeg"); fail("Creating should fail, as the document name is invalid."); } catch (IllegalStateException e) { // Expected. } } public void testAddDirectory() throws IOException { final String documentId = mArchive.createDocument(createArchiveId("/").toDocumentId(), Document.MIME_TYPE_DIR, "dir"); { final Cursor cursor = mArchive.queryDocument(documentId, null); assertTrue(cursor.moveToFirst()); assertEquals(documentId, cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID))); assertEquals("dir", cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME))); assertEquals(Document.MIME_TYPE_DIR, cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE))); assertEquals(0, cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE))); } { final Cursor cursor = mArchive.queryChildDocuments( createArchiveId("/").toDocumentId(), null, null); assertTrue(cursor.moveToFirst()); assertEquals(documentId, cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID))); assertEquals("dir", cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME))); assertEquals(Document.MIME_TYPE_DIR, cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE))); assertEquals(0, cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE))); } mArchive.close(); // Verify archive. ZipFile zip = null; try { zip = new ZipFile(mFile); final Enumeration<? extends ZipEntry> entries = zip.entries(); assertTrue(entries.hasMoreElements()); final ZipEntry entry = entries.nextElement(); assertEquals("dir/", entry.getName()); assertFalse(entries.hasMoreElements()); } finally { if (zip != null) { zip.close(); } } } public void testAddFile() throws IOException, InterruptedException { final String documentId = mArchive.createDocument(createArchiveId("/").toDocumentId(), "text/plain", "hoge.txt"); { final Cursor cursor = mArchive.queryDocument(documentId, null); assertTrue(cursor.moveToFirst()); assertEquals(documentId, cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID))); assertEquals("hoge.txt", cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME))); assertEquals("text/plain", cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE))); assertEquals(0, cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE))); } try { mArchive.openDocument(documentId, "r", null); fail("Should fail when opened for reading!"); } catch (IllegalArgumentException e) { // Expected. } final ParcelFileDescriptor fd = mArchive.openDocument(documentId, "w", null); try (ParcelFileDescriptor.AutoCloseOutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(fd)) { outputStream.write("Hello world!".getBytes()); } try { mArchive.openDocument(documentId, "w", null); fail("Should fail when opened for the second time!"); } catch (IllegalStateException e) { // Expected. } // Wait until the pipe thread fully writes all the data from the pipe. // TODO: Maybe add some method in WriteableArchive to wait until the executor // completes the job? Thread.sleep(500); { final Cursor cursor = mArchive.queryDocument(documentId, null); assertTrue(cursor.moveToFirst()); assertEquals(12, cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE))); } mArchive.close(); // Verify archive. ZipFile zip = null; try { try { zip = new ZipFile(mFile); } catch (Exception e) { throw new IOException(mFile.getAbsolutePath()); } final Enumeration<? extends ZipEntry> entries = zip.entries(); assertTrue(entries.hasMoreElements()); final ZipEntry entry = entries.nextElement(); assertEquals("hoge.txt", entry.getName()); assertFalse(entries.hasMoreElements()); final InputStream inputStream = zip.getInputStream(entry); final Scanner scanner = new Scanner(inputStream); assertEquals("Hello world!", scanner.nextLine()); assertFalse(scanner.hasNext()); } finally { if (zip != null) { zip.close(); } } } public void testAddFile_empty() throws IOException, Exception { final String documentId = mArchive.createDocument(createArchiveId("/").toDocumentId(), "text/plain", "hoge.txt"); mArchive.close(); // Verify archive. ZipFile zip = null; try { try { zip = new ZipFile(mFile); } catch (Exception e) { throw new IOException(mFile.getAbsolutePath()); } final Enumeration<? extends ZipEntry> entries = zip.entries(); assertTrue(entries.hasMoreElements()); final ZipEntry entry = entries.nextElement(); assertEquals("hoge.txt", entry.getName()); assertFalse(entries.hasMoreElements()); final InputStream inputStream = zip.getInputStream(entry); final Scanner scanner = new Scanner(inputStream); assertFalse(scanner.hasNext()); } finally { if (zip != null) { zip.close(); } } } }