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