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