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.archives; 18 19 import static junit.framework.Assert.assertEquals; 20 import static junit.framework.Assert.assertFalse; 21 import static junit.framework.Assert.assertNotNull; 22 import static junit.framework.Assert.assertNull; 23 import static junit.framework.Assert.assertTrue; 24 import static junit.framework.Assert.fail; 25 26 import android.content.ContentProviderClient; 27 import android.content.ContentResolver; 28 import android.content.Context; 29 import android.database.ContentObserver; 30 import android.database.Cursor; 31 import android.net.Uri; 32 import android.os.Bundle; 33 import android.os.ParcelFileDescriptor; 34 import android.os.RemoteException; 35 import android.os.SystemClock; 36 import android.provider.DocumentsContract; 37 import android.support.test.InstrumentationRegistry; 38 import android.support.test.filters.MediumTest; 39 import android.support.test.runner.AndroidJUnit4; 40 import android.text.TextUtils; 41 42 import org.junit.After; 43 import org.junit.Before; 44 import org.junit.Test; 45 import org.junit.runner.RunWith; 46 47 import java.util.concurrent.CountDownLatch; 48 import java.util.concurrent.ExecutorService; 49 import java.util.concurrent.Executors; 50 import java.util.concurrent.TimeUnit; 51 52 @RunWith(AndroidJUnit4.class) 53 @MediumTest 54 public class ArchivesProviderTest { 55 56 private Context mContext; 57 private ExecutorService mExecutor = null; 58 59 @Before setUp()60 public void setUp() throws Exception { 61 mContext = InstrumentationRegistry.getContext(); 62 mExecutor = Executors.newSingleThreadExecutor(); 63 } 64 65 @After tearDown()66 public void tearDown() throws Exception { 67 mExecutor.shutdown(); 68 assertTrue(mExecutor.awaitTermination(3 /* timeout */, TimeUnit.SECONDS)); 69 } 70 71 @Test testQueryRoots()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 testOpen_Success()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 testOpen_Failure()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 testOpen_ClosesOnRelease()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 testNoNotificationAfterAllReleased()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