1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.documentsui.archives;
18 
19 import com.android.documentsui.archives.WriteableArchive;
20 import com.android.documentsui.tests.R;
21 
22 import android.database.Cursor;
23 import android.content.Context;
24 import android.net.Uri;
25 import android.os.ParcelFileDescriptor;
26 import android.provider.DocumentsContract.Document;
27 import android.support.test.InstrumentationRegistry;
28 import android.test.AndroidTestCase;
29 import android.test.suitebuilder.annotation.MediumTest;
30 
31 import java.io.File;
32 import java.io.FileOutputStream;
33 import java.io.IOException;
34 import java.io.InputStream;
35 import java.util.Enumeration;
36 import java.util.Scanner;
37 import java.util.concurrent.ExecutorService;
38 import java.util.concurrent.Executors;
39 import java.util.concurrent.TimeUnit;
40 import java.util.zip.ZipEntry;
41 import java.util.zip.ZipFile;
42 
43 @MediumTest
44 public class WriteableArchiveTest extends AndroidTestCase {
45     private static final Uri ARCHIVE_URI = Uri.parse("content://i/love/strawberries");
46     private static final String NOTIFICATION_URI =
47             "content://com.android.documentsui.archives/notification-uri";
48     private ExecutorService mExecutor = null;
49     private Archive mArchive = null;
50     private TestUtils mTestUtils = null;
51     private File mFile = null;
52 
53     @Override
setUp()54     public void setUp() throws Exception {
55         super.setUp();
56         mExecutor = Executors.newSingleThreadExecutor();
57         mTestUtils = new TestUtils(InstrumentationRegistry.getTargetContext(),
58                 InstrumentationRegistry.getContext(), mExecutor);
59         mFile = mTestUtils.createTemporaryFile();
60 
61         mArchive = WriteableArchive.createForParcelFileDescriptor(
62                 InstrumentationRegistry.getTargetContext(),
63                 ParcelFileDescriptor.open(mFile, ParcelFileDescriptor.MODE_WRITE_ONLY),
64                 ARCHIVE_URI,
65                 ParcelFileDescriptor.MODE_WRITE_ONLY,
66                 Uri.parse(NOTIFICATION_URI));
67     }
68 
69     @Override
tearDown()70     public void tearDown() throws Exception {
71         mExecutor.shutdown();
72         assertTrue(mExecutor.awaitTermination(3 /* timeout */, TimeUnit.SECONDS));
73         if (mFile != null) {
74             mFile.delete();
75         }
76         if (mArchive != null) {
77             mArchive.close();
78         }
79         super.tearDown();
80     }
81 
createArchiveId(String path)82     public static ArchiveId createArchiveId(String path) {
83         return new ArchiveId(ARCHIVE_URI, ParcelFileDescriptor.MODE_WRITE_ONLY, path);
84     }
85 
testCreateDocument()86     public void testCreateDocument() throws IOException {
87         final String dirDocumentId = mArchive.createDocument(createArchiveId("/").toDocumentId(),
88                 Document.MIME_TYPE_DIR, "dir");
89         assertEquals(createArchiveId("/dir/").toDocumentId(), dirDocumentId);
90 
91         final String documentId = mArchive.createDocument(dirDocumentId, "image/jpeg", "test.jpeg");
92         assertEquals(createArchiveId("/dir/test.jpeg").toDocumentId(), documentId);
93 
94         try {
95             mArchive.createDocument(dirDocumentId,
96                     "image/jpeg", "test.jpeg");
97             fail("Creating should fail, as the document already exists.");
98         } catch (IllegalStateException e) {
99             // Expected.
100         }
101 
102         try {
103             mArchive.createDocument(createArchiveId("/").toDocumentId(),
104                     "image/jpeg", "test.jpeg/");
105             fail("Creating should fail, as the document name is invalid.");
106         } catch (IllegalStateException e) {
107             // Expected.
108         }
109 
110         try {
111             mArchive.createDocument(createArchiveId("/").toDocumentId(),
112                     Document.MIME_TYPE_DIR, "test/");
113             fail("Creating should fail, as the document name is invalid.");
114         } catch (IllegalStateException e) {
115             // Expected.
116         }
117 
118         try {
119             mArchive.createDocument(createArchiveId("/").toDocumentId(),
120                     Document.MIME_TYPE_DIR, "..");
121             fail("Creating should fail, as the document name is invalid.");
122         } catch (IllegalStateException e) {
123             // Expected.
124         }
125 
126         try {
127             mArchive.createDocument(createArchiveId("/").toDocumentId(),
128                     Document.MIME_TYPE_DIR, ".");
129             fail("Creating should fail, as the document name is invalid.");
130         } catch (IllegalStateException e) {
131             // Expected.
132         }
133 
134         try {
135             mArchive.createDocument(createArchiveId("/").toDocumentId(),
136                     Document.MIME_TYPE_DIR, "");
137             fail("Creating should fail, as the document name is invalid.");
138         } catch (IllegalStateException e) {
139             // Expected.
140         }
141 
142         try {
143             mArchive.createDocument(createArchiveId("/").toDocumentId(),
144                     "image/jpeg", "a/b.jpeg");
145             fail("Creating should fail, as the document name is invalid.");
146         } catch (IllegalStateException e) {
147             // Expected.
148         }
149     }
150 
testAddDirectory()151     public void testAddDirectory() throws IOException {
152         final String documentId = mArchive.createDocument(createArchiveId("/").toDocumentId(),
153                 Document.MIME_TYPE_DIR, "dir");
154 
155         {
156             final Cursor cursor = mArchive.queryDocument(documentId, null);
157             assertTrue(cursor.moveToFirst());
158             assertEquals(documentId,
159                     cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
160             assertEquals("dir",
161                     cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
162             assertEquals(Document.MIME_TYPE_DIR,
163                     cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
164             assertEquals(0,
165                     cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
166         }
167 
168         {
169             final Cursor cursor = mArchive.queryChildDocuments(
170                     createArchiveId("/").toDocumentId(), null, null);
171 
172             assertTrue(cursor.moveToFirst());
173             assertEquals(documentId,
174                     cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
175             assertEquals("dir",
176                     cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
177             assertEquals(Document.MIME_TYPE_DIR,
178                     cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
179             assertEquals(0,
180                     cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
181         }
182 
183         mArchive.close();
184 
185         // Verify archive.
186         ZipFile zip = null;
187         try {
188             zip = new ZipFile(mFile);
189             final Enumeration<? extends ZipEntry> entries = zip.entries();
190             assertTrue(entries.hasMoreElements());
191             final ZipEntry entry = entries.nextElement();
192             assertEquals("dir/", entry.getName());
193             assertFalse(entries.hasMoreElements());
194         } finally {
195             if (zip != null) {
196                 zip.close();
197             }
198         }
199     }
200 
testAddFile()201     public void testAddFile() throws IOException, InterruptedException {
202         final String documentId = mArchive.createDocument(createArchiveId("/").toDocumentId(),
203                 "text/plain", "hoge.txt");
204 
205         {
206             final Cursor cursor = mArchive.queryDocument(documentId, null);
207             assertTrue(cursor.moveToFirst());
208             assertEquals(documentId,
209                     cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
210             assertEquals("hoge.txt",
211                     cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
212             assertEquals("text/plain",
213                     cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
214             assertEquals(0,
215                     cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
216         }
217 
218         try {
219             mArchive.openDocument(documentId, "r", null);
220             fail("Should fail when opened for reading!");
221         } catch (IllegalArgumentException e) {
222             // Expected.
223         }
224 
225         final ParcelFileDescriptor fd = mArchive.openDocument(documentId, "w", null);
226         try (final ParcelFileDescriptor.AutoCloseOutputStream outputStream =
227                 new ParcelFileDescriptor.AutoCloseOutputStream(fd)) {
228             outputStream.write("Hello world!".getBytes());
229         }
230 
231         try {
232             mArchive.openDocument(documentId, "w", null);
233             fail("Should fail when opened for the second time!");
234         } catch (IllegalStateException e) {
235             // Expected.
236         }
237 
238         // Wait until the pipe thread fully writes all the data from the pipe.
239         // TODO: Maybe add some method in WriteableArchive to wait until the executor
240         // completes the job?
241         Thread.sleep(500);
242 
243         {
244             final Cursor cursor = mArchive.queryDocument(documentId, null);
245             assertTrue(cursor.moveToFirst());
246             assertEquals(12,
247                     cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
248         }
249 
250         mArchive.close();
251 
252         // Verify archive.
253         ZipFile zip = null;
254         try {
255             try {
256             zip = new ZipFile(mFile);
257             } catch (Exception e) {
258                 throw new IOException(mFile.getAbsolutePath());
259             }
260             final Enumeration<? extends ZipEntry> entries = zip.entries();
261             assertTrue(entries.hasMoreElements());
262             final ZipEntry entry = entries.nextElement();
263             assertEquals("hoge.txt", entry.getName());
264             assertFalse(entries.hasMoreElements());
265             final InputStream inputStream = zip.getInputStream(entry);
266             final Scanner scanner = new Scanner(inputStream);
267             assertEquals("Hello world!", scanner.nextLine());
268             assertFalse(scanner.hasNext());
269         } finally {
270             if (zip != null) {
271                 zip.close();
272             }
273         }
274     }
275 
testAddFile_empty()276     public void testAddFile_empty() throws IOException, Exception {
277         final String documentId = mArchive.createDocument(createArchiveId("/").toDocumentId(),
278                 "text/plain", "hoge.txt");
279         mArchive.close();
280 
281         // Verify archive.
282         ZipFile zip = null;
283         try {
284             try {
285             zip = new ZipFile(mFile);
286             } catch (Exception e) {
287                 throw new IOException(mFile.getAbsolutePath());
288             }
289             final Enumeration<? extends ZipEntry> entries = zip.entries();
290             assertTrue(entries.hasMoreElements());
291             final ZipEntry entry = entries.nextElement();
292             assertEquals("hoge.txt", entry.getName());
293             assertFalse(entries.hasMoreElements());
294             final InputStream inputStream = zip.getInputStream(entry);
295             final Scanner scanner = new Scanner(inputStream);
296             assertFalse(scanner.hasNext());
297         } finally {
298             if (zip != null) {
299                 zip.close();
300             }
301         }
302     }
303 }
304