1 /* 2 * Copyright (C) 2015 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 static com.google.common.truth.Truth.assertThat; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertFalse; 23 import static org.junit.Assert.assertTrue; 24 25 import android.database.Cursor; 26 import android.net.Uri; 27 import android.os.ParcelFileDescriptor; 28 import android.provider.DocumentsContract.Document; 29 import android.system.ErrnoException; 30 import android.system.Os; 31 import android.system.OsConstants; 32 import android.text.TextUtils; 33 34 import androidx.annotation.IdRes; 35 import androidx.test.InstrumentationRegistry; 36 import androidx.test.filters.MediumTest; 37 import androidx.test.runner.AndroidJUnit4; 38 39 import com.android.documentsui.tests.R; 40 41 import java.io.IOException; 42 import java.util.Scanner; 43 import java.util.concurrent.ExecutorService; 44 import java.util.concurrent.Executors; 45 import java.util.concurrent.TimeUnit; 46 47 import org.apache.commons.compress.archivers.ArchiveException; 48 import org.apache.commons.compress.compressors.CompressorException; 49 import org.junit.After; 50 import org.junit.Before; 51 import org.junit.Test; 52 import org.junit.runner.RunWith; 53 54 @RunWith(AndroidJUnit4.class) 55 @MediumTest 56 public class ReadableArchiveTest { 57 private static final Uri ARCHIVE_URI = Uri.parse("content://i/love/strawberries"); 58 private static final String NOTIFICATION_URI = 59 "content://com.android.documentsui.archives/notification-uri"; 60 private ExecutorService mExecutor = null; 61 private Archive mArchive = null; 62 private TestUtils mTestUtils = null; 63 64 @Before setUp()65 public void setUp() throws Exception { 66 mExecutor = Executors.newSingleThreadExecutor(); 67 mTestUtils = new TestUtils(InstrumentationRegistry.getTargetContext(), 68 InstrumentationRegistry.getContext(), mExecutor); 69 } 70 71 @After tearDown()72 public void tearDown() throws Exception { 73 mExecutor.shutdown(); 74 assertTrue(mExecutor.awaitTermination(3 /* timeout */, TimeUnit.SECONDS)); 75 if (mArchive != null) { 76 mArchive.close(); 77 } 78 } 79 createArchiveId(String path)80 public static ArchiveId createArchiveId(String path) { 81 return new ArchiveId(ARCHIVE_URI, ParcelFileDescriptor.MODE_READ_ONLY, path); 82 } 83 loadArchive(ParcelFileDescriptor descriptor, String mimeType)84 private void loadArchive(ParcelFileDescriptor descriptor, String mimeType) 85 throws IOException, CompressorException, ArchiveException { 86 mArchive = ReadableArchive.createForParcelFileDescriptor( 87 InstrumentationRegistry.getTargetContext(), 88 descriptor, 89 ARCHIVE_URI, 90 mimeType, 91 ParcelFileDescriptor.MODE_READ_ONLY, 92 Uri.parse(NOTIFICATION_URI)); 93 } 94 loadArchive(ParcelFileDescriptor descriptor)95 private void loadArchive(ParcelFileDescriptor descriptor) 96 throws IOException, CompressorException, ArchiveException { 97 loadArchive(descriptor, "application/zip"); 98 } 99 assertRowExist(Cursor cursor, String targetDocId)100 private static void assertRowExist(Cursor cursor, String targetDocId) { 101 assertTrue(cursor.moveToFirst()); 102 103 boolean found = false; 104 final int count = cursor.getCount(); 105 for (int i = 0; i < count; i++) { 106 cursor.moveToPosition(i); 107 if (TextUtils.equals(targetDocId, cursor.getString( 108 cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)))) { 109 found = true; 110 break; 111 } 112 } 113 114 assertTrue(targetDocId + " should be exists", found); 115 } 116 117 @Test testQueryChildDocument()118 public void testQueryChildDocument() 119 throws IOException, CompressorException, ArchiveException { 120 loadArchive(mTestUtils.getNonSeekableDescriptor(R.raw.archive)); 121 final Cursor cursor = mArchive.queryChildDocuments( 122 createArchiveId("/").toDocumentId(), null, null); 123 124 assertRowExist(cursor, createArchiveId("/file1.txt").toDocumentId()); 125 assertEquals("file1.txt", 126 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME))); 127 assertEquals("text/plain", 128 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE))); 129 assertEquals(13, 130 cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE))); 131 132 assertRowExist(cursor, createArchiveId("/dir1/").toDocumentId()); 133 assertEquals("dir1", 134 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME))); 135 assertEquals(Document.MIME_TYPE_DIR, 136 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE))); 137 assertEquals(0, 138 cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE))); 139 140 assertRowExist(cursor, createArchiveId("/dir2/").toDocumentId()); 141 assertEquals("dir2", 142 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME))); 143 assertEquals(Document.MIME_TYPE_DIR, 144 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE))); 145 assertEquals(0, 146 cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE))); 147 148 149 // Check if querying children works too. 150 final Cursor childCursor = mArchive.queryChildDocuments( 151 createArchiveId("/dir1/").toDocumentId(), null, null); 152 153 assertTrue(childCursor.moveToFirst()); 154 assertEquals( 155 createArchiveId("/dir1/cherries.txt").toDocumentId(), 156 childCursor.getString(childCursor.getColumnIndexOrThrow( 157 Document.COLUMN_DOCUMENT_ID))); 158 assertEquals("cherries.txt", 159 childCursor.getString(childCursor.getColumnIndexOrThrow( 160 Document.COLUMN_DISPLAY_NAME))); 161 assertEquals("text/plain", 162 childCursor.getString(childCursor.getColumnIndexOrThrow( 163 Document.COLUMN_MIME_TYPE))); 164 assertEquals(17, 165 childCursor.getInt(childCursor.getColumnIndexOrThrow(Document.COLUMN_SIZE))); 166 } 167 168 @Test testQueryChildDocument_NoDirs()169 public void testQueryChildDocument_NoDirs() 170 throws IOException, CompressorException, ArchiveException { 171 loadArchive(mTestUtils.getNonSeekableDescriptor(R.raw.no_dirs)); 172 final Cursor cursor = mArchive.queryChildDocuments( 173 createArchiveId("/").toDocumentId(), null, null); 174 175 assertTrue(cursor.moveToFirst()); 176 assertEquals( 177 createArchiveId("/dir1/").toDocumentId(), 178 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID))); 179 assertEquals("dir1", 180 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME))); 181 assertEquals(Document.MIME_TYPE_DIR, 182 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE))); 183 assertEquals(0, 184 cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE))); 185 assertFalse(cursor.moveToNext()); 186 187 final Cursor childCursor = mArchive.queryChildDocuments( 188 createArchiveId("/dir1/").toDocumentId(), null, null); 189 190 assertTrue(childCursor.moveToFirst()); 191 assertEquals( 192 createArchiveId("/dir1/dir2/").toDocumentId(), 193 childCursor.getString(childCursor.getColumnIndexOrThrow( 194 Document.COLUMN_DOCUMENT_ID))); 195 assertEquals("dir2", 196 childCursor.getString(childCursor.getColumnIndexOrThrow( 197 Document.COLUMN_DISPLAY_NAME))); 198 assertEquals(Document.MIME_TYPE_DIR, 199 childCursor.getString(childCursor.getColumnIndexOrThrow( 200 Document.COLUMN_MIME_TYPE))); 201 assertEquals(0, 202 childCursor.getInt(childCursor.getColumnIndexOrThrow(Document.COLUMN_SIZE))); 203 assertFalse(childCursor.moveToNext()); 204 205 final Cursor childCursor2 = mArchive.queryChildDocuments( 206 createArchiveId("/dir1/dir2/").toDocumentId(), 207 null, null); 208 209 assertTrue(childCursor2.moveToFirst()); 210 assertEquals( 211 createArchiveId("/dir1/dir2/cherries.txt").toDocumentId(), 212 childCursor2.getString(childCursor.getColumnIndexOrThrow( 213 Document.COLUMN_DOCUMENT_ID))); 214 assertFalse(childCursor2.moveToNext()); 215 } 216 217 @Test testQueryChildDocument_EmptyDirs()218 public void testQueryChildDocument_EmptyDirs() 219 throws IOException, CompressorException, ArchiveException { 220 loadArchive(mTestUtils.getNonSeekableDescriptor(R.raw.empty_dirs)); 221 final Cursor cursor = mArchive.queryChildDocuments( 222 createArchiveId("/").toDocumentId(), null, null); 223 224 assertTrue(cursor.moveToFirst()); 225 assertEquals( 226 createArchiveId("/dir1/").toDocumentId(), 227 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID))); 228 assertEquals("dir1", 229 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME))); 230 assertEquals(Document.MIME_TYPE_DIR, 231 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE))); 232 assertEquals(0, 233 cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE))); 234 assertFalse(cursor.moveToNext()); 235 236 final Cursor childCursor = mArchive.queryChildDocuments( 237 createArchiveId("/dir1/").toDocumentId(), null, null); 238 239 assertRowExist(childCursor, createArchiveId("/dir1/dir2/").toDocumentId()); 240 assertEquals("dir2", 241 childCursor.getString(childCursor.getColumnIndexOrThrow( 242 Document.COLUMN_DISPLAY_NAME))); 243 assertEquals(Document.MIME_TYPE_DIR, 244 childCursor.getString(childCursor.getColumnIndexOrThrow( 245 Document.COLUMN_MIME_TYPE))); 246 assertEquals(0, 247 childCursor.getInt(childCursor.getColumnIndexOrThrow(Document.COLUMN_SIZE))); 248 249 assertRowExist(childCursor, createArchiveId("/dir1/dir3/").toDocumentId()); 250 assertEquals( 251 createArchiveId("/dir1/dir3/").toDocumentId(), 252 childCursor.getString(childCursor.getColumnIndexOrThrow( 253 Document.COLUMN_DOCUMENT_ID))); 254 assertEquals("dir3", 255 childCursor.getString(childCursor.getColumnIndexOrThrow( 256 Document.COLUMN_DISPLAY_NAME))); 257 assertEquals(Document.MIME_TYPE_DIR, 258 childCursor.getString(childCursor.getColumnIndexOrThrow( 259 Document.COLUMN_MIME_TYPE))); 260 assertEquals(0, 261 childCursor.getInt(childCursor.getColumnIndexOrThrow(Document.COLUMN_SIZE))); 262 263 final Cursor childCursor2 = mArchive.queryChildDocuments( 264 createArchiveId("/dir1/dir2/").toDocumentId(), 265 null, null); 266 assertFalse(childCursor2.moveToFirst()); 267 268 final Cursor childCursor3 = mArchive.queryChildDocuments( 269 createArchiveId("/dir1/dir3/").toDocumentId(), 270 null, null); 271 assertFalse(childCursor3.moveToFirst()); 272 } 273 274 @Test testGetDocumentType()275 public void testGetDocumentType() throws IOException, CompressorException, ArchiveException { 276 loadArchive(mTestUtils.getNonSeekableDescriptor(R.raw.archive)); 277 assertEquals(Document.MIME_TYPE_DIR, mArchive.getDocumentType( 278 createArchiveId("/dir1/").toDocumentId())); 279 assertEquals("text/plain", mArchive.getDocumentType( 280 createArchiveId("/file1.txt").toDocumentId())); 281 } 282 283 @Test testIsChildDocument()284 public void testIsChildDocument() throws IOException, CompressorException, ArchiveException { 285 loadArchive(mTestUtils.getNonSeekableDescriptor(R.raw.archive)); 286 final String documentId = createArchiveId("/").toDocumentId(); 287 assertTrue(mArchive.isChildDocument(documentId, 288 createArchiveId("/dir1/").toDocumentId())); 289 assertFalse(mArchive.isChildDocument(documentId, 290 createArchiveId("/this-does-not-exist").toDocumentId())); 291 assertTrue(mArchive.isChildDocument( 292 createArchiveId("/dir1/").toDocumentId(), 293 createArchiveId("/dir1/cherries.txt").toDocumentId())); 294 assertTrue(mArchive.isChildDocument(documentId, 295 createArchiveId("/dir1/cherries.txt").toDocumentId())); 296 } 297 298 @Test testQueryDocument()299 public void testQueryDocument() throws IOException, CompressorException, ArchiveException { 300 loadArchive(mTestUtils.getNonSeekableDescriptor(R.raw.archive)); 301 final Cursor cursor = mArchive.queryDocument( 302 createArchiveId("/dir2/strawberries.txt").toDocumentId(), 303 null); 304 305 assertTrue(cursor.moveToFirst()); 306 assertEquals( 307 createArchiveId("/dir2/strawberries.txt").toDocumentId(), 308 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID))); 309 assertEquals("strawberries.txt", 310 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME))); 311 assertEquals("text/plain", 312 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE))); 313 assertEquals(21, 314 cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE))); 315 } 316 queryDocumentByResIdWithMimeTypeAndVerify(@dRes int resId, String mimeType)317 private void queryDocumentByResIdWithMimeTypeAndVerify(@IdRes int resId, String mimeType) 318 throws IOException, CompressorException, ArchiveException { 319 loadArchive(mTestUtils.getSeekableDescriptor(resId), 320 mimeType); 321 final String documentId = createArchiveId("/hello/hello.txt").toDocumentId(); 322 323 final Cursor cursor = mArchive.queryDocument(documentId, null); 324 cursor.moveToNext(); 325 326 assertThat(cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID))) 327 .isEqualTo(documentId); 328 assertThat(cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME))) 329 .isEqualTo("hello.txt"); 330 assertThat(cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE))) 331 .isEqualTo("text/plain"); 332 assertThat(cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE))) 333 .isEqualTo(48); 334 } 335 336 @Test archive_sevenZFile_containsList()337 public void archive_sevenZFile_containsList() 338 throws IOException, CompressorException, ArchiveException { 339 queryDocumentByResIdWithMimeTypeAndVerify(R.raw.hello_7z, 340 "application/x-7z-compressed"); 341 } 342 343 @Test archive_tar_containsList()344 public void archive_tar_containsList() 345 throws IOException, CompressorException, ArchiveException { 346 queryDocumentByResIdWithMimeTypeAndVerify(R.raw.hello_tar, "application/x-tar"); 347 } 348 349 @Test archive_tgz_containsList()350 public void archive_tgz_containsList() 351 throws IOException, CompressorException, ArchiveException { 352 queryDocumentByResIdWithMimeTypeAndVerify(R.raw.hello_tgz, 353 "application/x-compressed-tar"); 354 } 355 356 @Test archive_tarXz_containsList()357 public void archive_tarXz_containsList() 358 throws IOException, CompressorException, ArchiveException { 359 queryDocumentByResIdWithMimeTypeAndVerify(R.raw.hello_tar_xz, 360 "application/x-xz-compressed-tar"); 361 } 362 363 @Test archive_tarBz_containsList()364 public void archive_tarBz_containsList() 365 throws IOException, CompressorException, ArchiveException { 366 queryDocumentByResIdWithMimeTypeAndVerify(R.raw.hello_tar_bz2, 367 "application/x-bzip-compressed-tar"); 368 } 369 370 @Test archive_tarBrotli_containsList()371 public void archive_tarBrotli_containsList() 372 throws IOException, CompressorException, ArchiveException { 373 queryDocumentByResIdWithMimeTypeAndVerify(R.raw.hello_tar_br, 374 "application/x-brotli-compressed-tar"); 375 } 376 377 @Test testOpenDocument()378 public void testOpenDocument() 379 throws IOException, CompressorException, ArchiveException, ErrnoException { 380 loadArchive(mTestUtils.getSeekableDescriptor(R.raw.archive)); 381 commonTestOpenDocument(); 382 } 383 384 @Test testOpenDocument_NonSeekable()385 public void testOpenDocument_NonSeekable() 386 throws IOException, CompressorException, ArchiveException, ErrnoException { 387 loadArchive(mTestUtils.getNonSeekableDescriptor(R.raw.archive)); 388 commonTestOpenDocument(); 389 } 390 391 // Common part of testOpenDocument and testOpenDocument_NonSeekable. commonTestOpenDocument()392 void commonTestOpenDocument() throws IOException, ErrnoException { 393 final ParcelFileDescriptor descriptor = mArchive.openDocument( 394 createArchiveId("/dir2/strawberries.txt").toDocumentId(), 395 "r", null /* signal */); 396 assertTrue(Archive.canSeek(descriptor)); 397 try (final ParcelFileDescriptor.AutoCloseInputStream inputStream = 398 new ParcelFileDescriptor.AutoCloseInputStream(descriptor)) { 399 Os.lseek(descriptor.getFileDescriptor(), "I love ".length(), OsConstants.SEEK_SET); 400 assertEquals("strawberries!", new Scanner(inputStream).nextLine()); 401 Os.lseek(descriptor.getFileDescriptor(), 0, OsConstants.SEEK_SET); 402 assertEquals("I love strawberries!", new Scanner(inputStream).nextLine()); 403 } 404 } 405 406 @Test testCanSeek()407 public void testCanSeek() throws IOException { 408 assertTrue(Archive.canSeek(mTestUtils.getSeekableDescriptor(R.raw.archive))); 409 assertFalse(Archive.canSeek(mTestUtils.getNonSeekableDescriptor(R.raw.archive))); 410 } 411 412 @Test testBrokenArchive()413 public void testBrokenArchive() throws IOException, CompressorException, ArchiveException { 414 loadArchive(mTestUtils.getNonSeekableDescriptor(R.raw.archive)); 415 final Cursor cursor = mArchive.queryChildDocuments( 416 createArchiveId("/").toDocumentId(), null, null); 417 } 418 } 419