1 /* 2 * Copyright (C) 2018 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 android.provider.cts; 18 19 import static android.provider.cts.ProviderTestUtils.hash; 20 import static android.provider.cts.ProviderTestUtils.resolveVolumeName; 21 22 import static org.junit.Assert.assertArrayEquals; 23 import static org.junit.Assert.assertEquals; 24 import static org.junit.Assert.assertNotNull; 25 26 import android.content.ContentResolver; 27 import android.content.ContentUris; 28 import android.content.ContentValues; 29 import android.content.Context; 30 import android.database.ContentObserver; 31 import android.database.Cursor; 32 import android.net.Uri; 33 import android.os.Environment; 34 import android.os.FileUtils; 35 import android.platform.test.annotations.Presubmit; 36 import android.provider.MediaStore; 37 import android.provider.MediaStore.Downloads; 38 import android.provider.MediaStore.Files; 39 import android.provider.MediaStore.Images; 40 import android.provider.cts.MediaStoreUtils.PendingParams; 41 import android.provider.cts.MediaStoreUtils.PendingSession; 42 import android.util.Log; 43 44 import androidx.test.InstrumentationRegistry; 45 46 import org.junit.Assume; 47 import org.junit.Before; 48 import org.junit.Test; 49 import org.junit.runner.RunWith; 50 import org.junit.runners.Parameterized; 51 import org.junit.runners.Parameterized.Parameter; 52 import org.junit.runners.Parameterized.Parameters; 53 54 import java.io.ByteArrayOutputStream; 55 import java.io.File; 56 import java.io.FileInputStream; 57 import java.io.FileOutputStream; 58 import java.io.InputStream; 59 import java.io.OutputStream; 60 import java.io.PrintWriter; 61 import java.nio.charset.StandardCharsets; 62 import java.util.concurrent.CountDownLatch; 63 import java.util.concurrent.TimeUnit; 64 65 @Presubmit 66 @RunWith(Parameterized.class) 67 public class MediaStore_DownloadsTest { 68 private static final String TAG = MediaStore_DownloadsTest.class.getSimpleName(); 69 private static final long NOTIFY_TIMEOUT_MILLIS = 4000; 70 71 private Context mContext; 72 private ContentResolver mContentResolver; 73 private File mDownloadsDir; 74 private File mPicturesDir; 75 private CountDownLatch mCountDownLatch; 76 private int mInitialDownloadsCount; 77 78 private Uri mExternalImages; 79 private Uri mExternalDownloads; 80 81 @Parameter(0) 82 public String mVolumeName; 83 84 @Parameters data()85 public static Iterable<? extends Object> data() { 86 return ProviderTestUtils.getSharedVolumeNames(); 87 } 88 89 @Before setUp()90 public void setUp() throws Exception { 91 mContext = InstrumentationRegistry.getTargetContext(); 92 mContentResolver = mContext.getContentResolver(); 93 94 Log.d(TAG, "Using volume " + mVolumeName); 95 mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName); 96 mExternalDownloads = MediaStore.Downloads.getContentUri(mVolumeName); 97 98 mDownloadsDir = new File(MediaStore.getVolumePath(resolveVolumeName(mVolumeName)), 99 Environment.DIRECTORY_DOWNLOADS); 100 mPicturesDir = new File(MediaStore.getVolumePath(resolveVolumeName(mVolumeName)), 101 Environment.DIRECTORY_PICTURES); 102 mDownloadsDir.mkdirs(); 103 mPicturesDir.mkdirs(); 104 mInitialDownloadsCount = getInitialDownloadsCount(); 105 } 106 107 @Test testScannedDownload()108 public void testScannedDownload() throws Exception { 109 Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName) 110 || MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName)); 111 112 final File downloadFile = new File(mDownloadsDir, "colors.txt"); 113 downloadFile.createNewFile(); 114 final String fileContents = "RED;GREEN;BLUE"; 115 try (final PrintWriter pw = new PrintWriter(downloadFile)) { 116 pw.print(fileContents); 117 } 118 verifyScannedDownload(downloadFile); 119 } 120 121 @Test testScannedMediaDownload()122 public void testScannedMediaDownload() throws Exception { 123 Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName) 124 || MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName)); 125 126 final File downloadFile = new File(mDownloadsDir, "scenery.png"); 127 downloadFile.createNewFile(); 128 try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery); 129 OutputStream out = new FileOutputStream(downloadFile)) { 130 FileUtils.copy(in, out); 131 } 132 verifyScannedDownload(downloadFile); 133 } 134 135 @Test testGetContentUri()136 public void testGetContentUri() throws Exception { 137 Cursor c; 138 assertNotNull(c = mContentResolver.query(mExternalDownloads, 139 null, null, null, null)); 140 c.close(); 141 } 142 143 @Test testMediaInDownloadsDir()144 public void testMediaInDownloadsDir() throws Exception { 145 Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName) 146 || MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName)); 147 148 final String displayName = "cts" + System.nanoTime(); 149 final Uri insertUri = insertImage(displayName, "test image", 150 new File(mDownloadsDir, displayName + ".jpg"), "image/jpeg", R.raw.scenery); 151 final String displayName2 = "cts" + System.nanoTime(); 152 final Uri insertUri2 = insertImage(displayName2, "test image2", 153 new File(mPicturesDir, displayName2 + ".jpg"), "image/jpeg", R.raw.volantis); 154 155 try (Cursor cursor = mContentResolver.query(mExternalDownloads, 156 null, "title LIKE ?1", new String[] { displayName }, null)) { 157 assertEquals(1, cursor.getCount()); 158 cursor.moveToNext(); 159 assertEquals("image/jpeg", 160 cursor.getString(cursor.getColumnIndex(Images.Media.MIME_TYPE))); 161 } 162 163 assertEquals(1, mContentResolver.delete(insertUri, null, null)); 164 try (Cursor cursor = mContentResolver.query(mExternalDownloads, 165 null, null, null, null)) { 166 assertEquals(mInitialDownloadsCount, cursor.getCount()); 167 } 168 } 169 170 @Test testInsertDownload()171 public void testInsertDownload() throws Exception { 172 final String content = "<html><body>Content</body></html>"; 173 final String displayName = "cts" + System.nanoTime(); 174 final String mimeType = "text/html"; 175 final Uri downloadUri = Uri.parse("https://developer.android.com/overview.html"); 176 final Uri refererUri = Uri.parse("https://www.android.com"); 177 178 final PendingParams params = new PendingParams( 179 mExternalDownloads, displayName, mimeType); 180 params.setDownloadUri(downloadUri); 181 params.setRefererUri(refererUri); 182 183 final Uri pendingUri = MediaStoreUtils.createPending(mContext, params); 184 assertNotNull(pendingUri); 185 final Uri publishUri; 186 try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) { 187 try (PrintWriter pw = new PrintWriter(session.openOutputStream())) { 188 pw.print(content); 189 } 190 try (OutputStream out = session.openOutputStream()) { 191 out.write(content.getBytes(StandardCharsets.UTF_8)); 192 } 193 publishUri = session.publish(); 194 } 195 196 try (Cursor cursor = mContentResolver.query(publishUri, null, null, null, null)) { 197 assertEquals(1, cursor.getCount()); 198 199 cursor.moveToNext(); 200 assertEquals(mimeType, 201 cursor.getString(cursor.getColumnIndex(Downloads.MIME_TYPE))); 202 assertEquals(displayName + ".html", 203 cursor.getString(cursor.getColumnIndex(Downloads.DISPLAY_NAME))); 204 assertEquals(downloadUri.toString(), 205 cursor.getString(cursor.getColumnIndex(Downloads.DOWNLOAD_URI))); 206 assertEquals(refererUri.toString(), 207 cursor.getString(cursor.getColumnIndex(Downloads.REFERER_URI))); 208 } 209 210 final ByteArrayOutputStream actual = new ByteArrayOutputStream(); 211 try (InputStream in = mContentResolver.openInputStream(publishUri)) { 212 final byte[] buf = new byte[512]; 213 int bytesRead; 214 while ((bytesRead = in.read(buf)) != -1) { 215 actual.write(buf, 0, bytesRead); 216 } 217 } 218 assertEquals(content, actual.toString(StandardCharsets.UTF_8.name())); 219 } 220 221 @Test testUpdateDownload()222 public void testUpdateDownload() throws Exception { 223 final String displayName = "cts" + System.nanoTime(); 224 final PendingParams params = new PendingParams( 225 mExternalDownloads, displayName, "video/3gpp"); 226 final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp"); 227 params.setDownloadUri(downloadUri); 228 229 final Uri pendingUri = MediaStoreUtils.createPending(mContext, params); 230 assertNotNull(pendingUri); 231 final Uri publishUri; 232 try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) { 233 try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo); 234 OutputStream out = session.openOutputStream()) { 235 android.os.FileUtils.copy(in, out); 236 } 237 publishUri = session.publish(); 238 } 239 240 final ContentValues updateValues = new ContentValues(); 241 updateValues.put(MediaStore.Files.FileColumns.MEDIA_TYPE, 242 MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO); 243 updateValues.put(Downloads.MIME_TYPE, "audio/3gpp"); 244 assertEquals(1, mContentResolver.update(publishUri, updateValues, null, null)); 245 246 try (Cursor cursor = mContentResolver.query(publishUri, 247 null, null, null, null)) { 248 assertEquals(1, cursor.getCount()); 249 cursor.moveToNext(); 250 assertEquals("audio/3gpp", 251 cursor.getString(cursor.getColumnIndex(Downloads.MIME_TYPE))); 252 assertEquals(downloadUri.toString(), 253 cursor.getString(cursor.getColumnIndex(Downloads.DOWNLOAD_URI))); 254 } 255 } 256 257 @Test testDeleteDownload()258 public void testDeleteDownload() throws Exception { 259 final String displayName = "cts" + System.nanoTime(); 260 final PendingParams params = new PendingParams( 261 mExternalDownloads, displayName, "video/3gp"); 262 final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp"); 263 params.setDownloadUri(downloadUri); 264 265 final Uri pendingUri = MediaStoreUtils.createPending(mContext, params); 266 assertNotNull(pendingUri); 267 final Uri publishUri; 268 try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) { 269 try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo); 270 OutputStream out = session.openOutputStream()) { 271 android.os.FileUtils.copy(in, out); 272 } 273 publishUri = session.publish(); 274 } 275 276 assertEquals(1, mContentResolver.delete(publishUri, null, null)); 277 try (Cursor cursor = mContentResolver.query(mExternalDownloads, 278 null, null, null, null)) { 279 assertEquals(mInitialDownloadsCount, cursor.getCount()); 280 } 281 } 282 283 @Test testNotifyChange()284 public void testNotifyChange() throws Exception { 285 final ContentObserver observer = new ContentObserver(null) { 286 @Override 287 public void onChange(boolean selfChange, Uri uri) { 288 super.onChange(selfChange, uri); 289 mCountDownLatch.countDown(); 290 } 291 }; 292 mContentResolver.registerContentObserver(mExternalDownloads, true, observer); 293 mContentResolver.registerContentObserver(MediaStore.AUTHORITY_URI, false, observer); 294 final Uri volumeUri = MediaStore.AUTHORITY_URI.buildUpon() 295 .appendPath(mVolumeName) 296 .build(); 297 mContentResolver.registerContentObserver(volumeUri, false, observer); 298 299 mCountDownLatch = new CountDownLatch(1); 300 final String displayName = "cts" + System.nanoTime(); 301 final PendingParams params = new PendingParams( 302 mExternalDownloads, displayName, "video/3gp"); 303 final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp"); 304 params.setDownloadUri(downloadUri); 305 final Uri pendingUri = MediaStoreUtils.createPending(mContext, params); 306 assertNotNull(pendingUri); 307 final Uri publishUri; 308 try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) { 309 try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo); 310 OutputStream out = session.openOutputStream()) { 311 android.os.FileUtils.copy(in, out); 312 } 313 publishUri = session.publish(); 314 } 315 mCountDownLatch.await(NOTIFY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 316 317 mCountDownLatch = new CountDownLatch(1); 318 final ContentValues updateValues = new ContentValues(); 319 updateValues.put(Files.FileColumns.MEDIA_TYPE, Files.FileColumns.MEDIA_TYPE_AUDIO); 320 updateValues.put(Downloads.MIME_TYPE, "audio/3gp"); 321 assertEquals(1, mContentResolver.update(publishUri, updateValues, null, null)); 322 mCountDownLatch.await(NOTIFY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 323 324 mCountDownLatch = new CountDownLatch(1); 325 assertEquals(1, mContentResolver.delete(publishUri, null, null)); 326 mCountDownLatch.await(NOTIFY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 327 } 328 getInitialDownloadsCount()329 private int getInitialDownloadsCount() { 330 try (Cursor cursor = mContentResolver.query(mExternalDownloads, 331 null, null, null, null)) { 332 return cursor.getCount(); 333 } 334 } 335 insertImage(String displayName, String description, File file, String mimeType, int resourceId)336 private Uri insertImage(String displayName, String description, 337 File file, String mimeType, int resourceId) throws Exception { 338 file.createNewFile(); 339 try (InputStream in = mContext.getResources().openRawResource(resourceId); 340 OutputStream out = new FileOutputStream(file)) { 341 FileUtils.copy(in, out); 342 } 343 344 final ContentValues values = new ContentValues(); 345 values.put(Images.Media.DISPLAY_NAME, displayName); 346 values.put(Images.Media.TITLE, displayName); 347 values.put(Images.Media.DESCRIPTION, description); 348 values.put(Images.Media.DATA, file.getAbsolutePath()); 349 values.put(Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000); 350 values.put(Images.Media.DATE_MODIFIED, System.currentTimeMillis() / 1000); 351 values.put(Images.Media.MIME_TYPE, mimeType); 352 353 final Uri insertUri = mContentResolver.insert(mExternalImages, values); 354 assertNotNull(insertUri); 355 return insertUri; 356 } 357 verifyScannedDownload(File file)358 private void verifyScannedDownload(File file) throws Exception { 359 final Uri mediaStoreUri = ProviderTestUtils.scanFile(file); 360 Log.e(TAG, "Scanned file " + file.getAbsolutePath() + ": " + mediaStoreUri); 361 assertArrayEquals("File hashes should match for " + file + " and " + mediaStoreUri, 362 hash(new FileInputStream(file)), 363 hash(mContentResolver.openInputStream(mediaStoreUri))); 364 365 // Verify the file is part of downloads collection. 366 final long id = ContentUris.parseId(mediaStoreUri); 367 final Cursor cursor = mContentResolver.query(mExternalDownloads, 368 null, MediaStore.Downloads._ID + "=" + id, null, null); 369 assertEquals(1, cursor.getCount()); 370 } 371 } 372