1 /* 2 * Copyright (C) 2016 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; 18 19 import static android.content.ContentResolver.wrap; 20 21 import static com.google.common.truth.Truth.assertThat; 22 23 import static junit.framework.Assert.assertEquals; 24 import static junit.framework.Assert.assertFalse; 25 import static junit.framework.Assert.assertNotNull; 26 import static junit.framework.Assert.assertNull; 27 import static junit.framework.Assert.assertTrue; 28 import static junit.framework.Assert.fail; 29 30 import android.content.ContentProviderClient; 31 import android.content.ContentResolver; 32 import android.content.Context; 33 import android.database.ContentObserver; 34 import android.database.Cursor; 35 import android.media.ExifInterface; 36 import android.net.Uri; 37 import android.os.Bundle; 38 import android.os.ParcelFileDescriptor; 39 import android.os.RemoteException; 40 import android.provider.DocumentsContract; 41 import android.text.TextUtils; 42 43 import androidx.test.InstrumentationRegistry; 44 import androidx.test.filters.MediumTest; 45 import androidx.test.runner.AndroidJUnit4; 46 47 import com.android.documentsui.archives.ArchivesProvider; 48 import com.android.documentsui.archives.ResourcesProvider; 49 50 import org.junit.After; 51 import org.junit.Before; 52 import org.junit.Test; 53 import org.junit.runner.RunWith; 54 55 import java.util.concurrent.CountDownLatch; 56 import java.util.concurrent.ExecutorService; 57 import java.util.concurrent.Executors; 58 import java.util.concurrent.TimeUnit; 59 60 @RunWith(AndroidJUnit4.class) 61 @MediumTest 62 public class ArchivesProviderTest { 63 64 private Context mContext; 65 private ExecutorService mExecutor = null; 66 67 @Before setUp()68 public void setUp() throws Exception { 69 mContext = InstrumentationRegistry.getContext(); 70 mExecutor = Executors.newSingleThreadExecutor(); 71 } 72 73 @After tearDown()74 public void tearDown() throws Exception { 75 mExecutor.shutdown(); 76 assertTrue(mExecutor.awaitTermination(3 /* timeout */, TimeUnit.SECONDS)); 77 } 78 79 @Test testQueryRoots()80 public void testQueryRoots() throws InterruptedException, RemoteException { 81 final ContentResolver resolver = mContext.getContentResolver(); 82 final Uri rootsUri = DocumentsContract.buildRootsUri(ArchivesProvider.AUTHORITY); 83 try (ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 84 rootsUri)) { 85 Cursor cursor = client.query(rootsUri, null, null, null, null, null); 86 assertNotNull("Cursor must not be null.", cursor); 87 assertEquals(0, cursor.getCount()); 88 } 89 } 90 91 @Test testOpen_Success()92 public void testOpen_Success() throws InterruptedException { 93 final Uri sourceUri = DocumentsContract.buildDocumentUri( 94 ResourcesProvider.AUTHORITY, "archive.zip"); 95 final Uri archiveUri = ArchivesProvider.buildUriForArchive(sourceUri, 96 ParcelFileDescriptor.MODE_READ_ONLY); 97 98 final Uri childrenUri = DocumentsContract.buildChildDocumentsUri( 99 ArchivesProvider.AUTHORITY, DocumentsContract.getDocumentId(archiveUri)); 100 101 final ContentResolver resolver = mContext.getContentResolver(); 102 final CountDownLatch latch = new CountDownLatch(1); 103 104 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 105 archiveUri); 106 ArchivesProvider.acquireArchive(client, archiveUri); 107 108 { 109 final Cursor cursor = resolver.query(childrenUri, null, null, null, null, null); 110 assertNotNull("Cursor must not be null. File not found?", cursor); 111 112 assertEquals(0, cursor.getCount()); 113 final Bundle extras = cursor.getExtras(); 114 assertEquals(true, extras.getBoolean(DocumentsContract.EXTRA_LOADING, false)); 115 assertNull(extras.getString(DocumentsContract.EXTRA_ERROR)); 116 117 final Uri notificationUri = cursor.getNotificationUri(); 118 assertNotNull(notificationUri); 119 120 resolver.registerContentObserver(notificationUri, false, new ContentObserver(null) { 121 @Override 122 public void onChange(boolean selfChange, Uri uri) { 123 latch.countDown(); 124 } 125 }); 126 } 127 128 latch.await(3, TimeUnit.SECONDS); 129 { 130 final Cursor cursor = resolver.query(childrenUri, null, null, null, null, null); 131 assertNotNull("Cursor must not be null. File not found?", cursor); 132 133 assertEquals(3, cursor.getCount()); 134 final Bundle extras = cursor.getExtras(); 135 assertEquals(false, extras.getBoolean(DocumentsContract.EXTRA_LOADING, false)); 136 assertNull(extras.getString(DocumentsContract.EXTRA_ERROR)); 137 } 138 139 ArchivesProvider.releaseArchive(client, archiveUri); 140 client.release(); 141 } 142 143 @Test testOpen_Failure()144 public void testOpen_Failure() throws InterruptedException { 145 final Uri sourceUri = DocumentsContract.buildDocumentUri( 146 ResourcesProvider.AUTHORITY, "broken.zip"); 147 final Uri archiveUri = ArchivesProvider.buildUriForArchive(sourceUri, 148 ParcelFileDescriptor.MODE_READ_ONLY); 149 150 final Uri childrenUri = DocumentsContract.buildChildDocumentsUri( 151 ArchivesProvider.AUTHORITY, DocumentsContract.getDocumentId(archiveUri)); 152 153 final ContentResolver resolver = mContext.getContentResolver(); 154 final CountDownLatch latch = new CountDownLatch(1); 155 156 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 157 archiveUri); 158 ArchivesProvider.acquireArchive(client, archiveUri); 159 160 { 161 // TODO: Close this and any other cursor in this file. 162 final Cursor cursor = resolver.query(childrenUri, null, null, null, null, null); 163 assertNotNull("Cursor must not be null. File not found?", cursor); 164 165 assertEquals(0, cursor.getCount()); 166 final Bundle extras = cursor.getExtras(); 167 assertEquals(true, extras.getBoolean(DocumentsContract.EXTRA_LOADING, false)); 168 assertNull(extras.getString(DocumentsContract.EXTRA_ERROR)); 169 170 final Uri notificationUri = cursor.getNotificationUri(); 171 assertNotNull(notificationUri); 172 173 resolver.registerContentObserver(notificationUri, false, new ContentObserver(null) { 174 @Override 175 public void onChange(boolean selfChange, Uri uri) { 176 latch.countDown(); 177 } 178 }); 179 } 180 181 latch.await(3, TimeUnit.SECONDS); 182 { 183 final Cursor cursor = resolver.query(childrenUri, null, null, null, null, null); 184 assertNotNull("Cursor must not be null. File not found?", cursor); 185 186 assertEquals(0, cursor.getCount()); 187 final Bundle extras = cursor.getExtras(); 188 assertEquals(false, extras.getBoolean(DocumentsContract.EXTRA_LOADING, false)); 189 assertFalse(TextUtils.isEmpty(extras.getString(DocumentsContract.EXTRA_ERROR))); 190 } 191 192 ArchivesProvider.releaseArchive(client, archiveUri); 193 client.release(); 194 } 195 196 @Test testOpen_ClosesOnRelease()197 public void testOpen_ClosesOnRelease() throws InterruptedException { 198 final Uri sourceUri = DocumentsContract.buildDocumentUri( 199 ResourcesProvider.AUTHORITY, "archive.zip"); 200 final Uri archiveUri = ArchivesProvider.buildUriForArchive(sourceUri, 201 ParcelFileDescriptor.MODE_READ_ONLY); 202 203 final Uri childrenUri = DocumentsContract.buildChildDocumentsUri( 204 ArchivesProvider.AUTHORITY, DocumentsContract.getDocumentId(archiveUri)); 205 206 final ContentResolver resolver = mContext.getContentResolver(); 207 208 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 209 archiveUri); 210 211 // Acquire twice to ensure that the refcount works correctly. 212 ArchivesProvider.acquireArchive(client, archiveUri); 213 ArchivesProvider.acquireArchive(client, archiveUri); 214 215 { 216 final Cursor cursor = resolver.query(childrenUri, null, null, null, null, null); 217 assertNotNull("Cursor must not be null. File not found?", cursor); 218 } 219 220 ArchivesProvider.releaseArchive(client, archiveUri); 221 222 { 223 final Cursor cursor = resolver.query(childrenUri, null, null, null, null, null); 224 assertNotNull("Cursor must not be null. File not found?", cursor); 225 } 226 227 ArchivesProvider.releaseArchive(client, archiveUri); 228 229 try { 230 resolver.query(childrenUri, null, null, null, null, null); 231 fail("The archive was expected to be invalid on the last release call."); 232 } catch (IllegalStateException e) { 233 // Expected. 234 } 235 236 client.release(); 237 } 238 239 @Test testNoNotificationAfterAllReleased()240 public void testNoNotificationAfterAllReleased() throws InterruptedException, RemoteException { 241 final Uri sourceUri = DocumentsContract.buildDocumentUri( 242 ResourcesProvider.AUTHORITY, "archive.zip"); 243 final Uri archiveUri = ArchivesProvider.buildUriForArchive(sourceUri, 244 ParcelFileDescriptor.MODE_READ_ONLY); 245 246 final Uri childrenUri = DocumentsContract.buildChildDocumentsUri( 247 ArchivesProvider.AUTHORITY, DocumentsContract.getDocumentId(archiveUri)); 248 249 final ContentResolver resolver = mContext.getContentResolver(); 250 251 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 252 archiveUri); 253 254 ArchivesProvider.acquireArchive(client, archiveUri); 255 final Cursor cursor = client.query(childrenUri, null, null, null, null, null); 256 final Bundle extra = cursor.getExtras(); 257 assertTrue(extra.getBoolean(DocumentsContract.EXTRA_LOADING, false)); 258 final Uri notificationUri = cursor.getNotificationUri(); 259 260 ArchivesProvider.releaseArchive(client, archiveUri); 261 final CountDownLatch latch = new CountDownLatch(1); 262 resolver.registerContentObserver(notificationUri, false, new ContentObserver(null) { 263 @Override 264 public void onChange(boolean selfChange, Uri uri) { 265 latch.countDown(); 266 } 267 }); 268 269 // Assert that there is no notification if no one has acquired this archive and this wait 270 // times out. 271 assertFalse(latch.await(1, TimeUnit.SECONDS)); 272 273 client.release(); 274 } 275 getDocumentMetadata_byDocumentId_shouldMatchSize(String documentId)276 private void getDocumentMetadata_byDocumentId_shouldMatchSize(String documentId) 277 throws Exception { 278 final Uri sourceUri = DocumentsContract.buildDocumentUri( 279 ResourcesProvider.AUTHORITY, documentId); 280 final Uri archiveUri = ArchivesProvider.buildUriForArchive(sourceUri, 281 ParcelFileDescriptor.MODE_READ_ONLY); 282 283 final ContentResolver resolver = mContext.getContentResolver(); 284 final ContentProviderClient client = 285 resolver.acquireUnstableContentProviderClient(archiveUri); 286 287 ArchivesProvider.acquireArchive(client, archiveUri); 288 289 Uri archivedImageUri = Uri.parse( 290 "content://com.android.documentsui.archives/document/content%3A%2F%2F" 291 + "com.android.documentsui.archives.resourcesprovider%2F" 292 + "document%2F" + documentId + "%23268435456%23%2Ffreddy.jpg"); 293 294 Bundle metadata = DocumentsContract.getDocumentMetadata(wrap(client), archivedImageUri); 295 assertNotNull(metadata); 296 Bundle exif = metadata.getBundle(DocumentsContract.METADATA_EXIF); 297 assertNotNull(exif); 298 299 assertThat(exif.getInt(ExifInterface.TAG_IMAGE_WIDTH)).isEqualTo(3036); 300 assertThat(exif.getInt(ExifInterface.TAG_IMAGE_LENGTH)).isEqualTo(4048); 301 assertThat(exif.getString(ExifInterface.TAG_MODEL)).isEqualTo("Pixel"); 302 303 ArchivesProvider.releaseArchive(client, archiveUri); 304 client.close(); 305 } 306 307 @Test testGetDocumentMetadata()308 public void testGetDocumentMetadata() throws Exception { 309 getDocumentMetadata_byDocumentId_shouldMatchSize("images.zip"); 310 } 311 312 @Test getDocumentMetadata_sevenZFile_shouldMatchSize()313 public void getDocumentMetadata_sevenZFile_shouldMatchSize() throws Exception { 314 getDocumentMetadata_byDocumentId_shouldMatchSize("images.7z"); 315 } 316 317 @Test getDocumentMetadata_tgz_shouldMatchSize()318 public void getDocumentMetadata_tgz_shouldMatchSize() throws Exception { 319 getDocumentMetadata_byDocumentId_shouldMatchSize("images.tgz"); 320 } 321 322 @Test getDocumentMetadata_tar_shouldMatchSize()323 public void getDocumentMetadata_tar_shouldMatchSize() throws Exception { 324 getDocumentMetadata_byDocumentId_shouldMatchSize("images.tar"); 325 } 326 } 327