1 /* 2 * Copyright (C) 2011 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 package android.app.cts; 17 18 import static android.Manifest.permission.WRITE_MEDIA_STORAGE; 19 import static android.content.pm.PackageManager.PERMISSION_DENIED; 20 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 21 22 import static com.android.compatibility.common.util.SystemUtil.runShellCommand; 23 24 import static org.junit.Assert.assertEquals; 25 import static org.junit.Assert.assertFalse; 26 import static org.junit.Assert.assertNotNull; 27 import static org.junit.Assert.assertTrue; 28 import static org.junit.Assert.fail; 29 30 import android.app.DownloadManager; 31 import android.app.DownloadManager.Query; 32 import android.app.DownloadManager.Request; 33 import android.app.stubs.GetResultActivity; 34 import android.content.Context; 35 import android.content.Intent; 36 import android.content.IntentFilter; 37 import android.content.pm.ApplicationInfo; 38 import android.content.pm.PackageInfo; 39 import android.content.pm.PackageManager; 40 import android.content.pm.ProviderInfo; 41 import android.content.pm.ResolveInfo; 42 import android.database.Cursor; 43 import android.net.Uri; 44 import android.os.Environment; 45 import android.os.FileUtils; 46 import android.os.ParcelFileDescriptor; 47 import android.provider.DocumentsContract; 48 import android.provider.MediaStore; 49 import android.util.Log; 50 import android.util.LongSparseArray; 51 import android.util.Pair; 52 53 import androidx.test.filters.FlakyTest; 54 import androidx.test.runner.AndroidJUnit4; 55 import androidx.test.uiautomator.UiObject; 56 import androidx.test.uiautomator.UiObjectNotFoundException; 57 import androidx.test.uiautomator.UiSelector; 58 59 import com.android.compatibility.common.util.CddTest; 60 61 import org.junit.Assert; 62 import org.junit.Test; 63 import org.junit.runner.RunWith; 64 65 import java.io.File; 66 import java.io.FileNotFoundException; 67 import java.io.FileOutputStream; 68 import java.io.IOException; 69 import java.io.InputStream; 70 import java.io.OutputStream; 71 72 @RunWith(AndroidJUnit4.class) 73 public class DownloadManagerTest extends DownloadManagerTestBase { 74 private static final String DOWNLOAD_STORAGE_PROVIDER_AUTHORITY = 75 "com.android.providers.downloads.documents"; 76 @FlakyTest 77 @Test testDownloadManager()78 public void testDownloadManager() throws Exception { 79 final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver(); 80 try { 81 IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); 82 mContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED); 83 84 long goodId = mDownloadManager.enqueue(new Request(getGoodUrl())); 85 long badId = mDownloadManager.enqueue(new Request(getBadUrl())); 86 87 int allDownloads = getTotalNumberDownloads(); 88 assertEquals(2, allDownloads); 89 90 assertDownloadQueryableById(goodId); 91 assertDownloadQueryableById(badId); 92 93 receiver.waitForDownloadComplete(SHORT_TIMEOUT, goodId, badId); 94 95 assertDownloadQueryableByStatus(DownloadManager.STATUS_SUCCESSFUL); 96 assertDownloadQueryableByStatus(DownloadManager.STATUS_FAILED); 97 98 assertRemoveDownload(goodId, allDownloads - 1); 99 assertRemoveDownload(badId, allDownloads - 2); 100 } finally { 101 mContext.unregisterReceiver(receiver); 102 } 103 } 104 105 @FlakyTest 106 @Test testDownloadManagerSupportsHttp()107 public void testDownloadManagerSupportsHttp() throws Exception { 108 final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver(); 109 try { 110 IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); 111 mContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED); 112 113 long id = mDownloadManager.enqueue(new Request(getGoodUrl())); 114 115 assertEquals(1, getTotalNumberDownloads()); 116 117 assertDownloadQueryableById(id); 118 119 receiver.waitForDownloadComplete(SHORT_TIMEOUT, id); 120 121 assertDownloadQueryableByStatus(DownloadManager.STATUS_SUCCESSFUL); 122 123 assertRemoveDownload(id, 0); 124 } finally { 125 mContext.unregisterReceiver(receiver); 126 } 127 } 128 129 @FlakyTest 130 @Test testDownloadManagerSupportsHttpWithExternalWebServer()131 public void testDownloadManagerSupportsHttpWithExternalWebServer() throws Exception { 132 if (!hasInternetConnection()) { 133 Log.i(TAG, "testDownloadManagerSupportsHttpWithExternalWebServer() ignored on device without Internet"); 134 return; 135 } 136 137 // As a result of testDownloadManagerSupportsHttpsWithExternalWebServer relying on an 138 // external resource https://www.example.com this test uses http://www.example.com to help 139 // disambiguate errors from testDownloadManagerSupportsHttpsWithExternalWebServer. 140 141 final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver(); 142 try { 143 IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); 144 mContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED); 145 146 long id = mDownloadManager.enqueue(new Request(Uri.parse("http://www.example.com"))); 147 148 assertEquals(1, getTotalNumberDownloads()); 149 150 assertDownloadQueryableById(id); 151 152 receiver.waitForDownloadComplete(LONG_TIMEOUT, id); 153 154 assertDownloadQueryableByStatus(DownloadManager.STATUS_SUCCESSFUL); 155 156 assertRemoveDownload(id, 0); 157 } finally { 158 mContext.unregisterReceiver(receiver); 159 } 160 } 161 162 @Test testDownloadManagerSupportsHttpsWithExternalWebServer()163 public void testDownloadManagerSupportsHttpsWithExternalWebServer() throws Exception { 164 if (!hasInternetConnection()) { 165 Log.i(TAG, "testDownloadManagerSupportsHttpsWithExternalWebServer() ignored on device without Internet"); 166 return; 167 } 168 169 // For HTTPS, DownloadManager trusts only SSL server certs issued by CAs trusted by the 170 // system. Unfortunately, this means that it cannot trust the mock web server's SSL cert. 171 // Until this is resolved (e.g., by making it possible to specify additional CA certs to 172 // trust for a particular download), this test relies on https://www.example.com being 173 // operational and reachable from the Android under test. 174 175 final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver(); 176 try { 177 IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); 178 mContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED); 179 180 long id = mDownloadManager.enqueue(new Request(Uri.parse("https://www.example.com"))); 181 182 assertEquals(1, getTotalNumberDownloads()); 183 184 assertDownloadQueryableById(id); 185 186 receiver.waitForDownloadComplete(LONG_TIMEOUT, id); 187 188 assertDownloadQueryableByStatus(DownloadManager.STATUS_SUCCESSFUL); 189 190 assertRemoveDownload(id, 0); 191 } finally { 192 mContext.unregisterReceiver(receiver); 193 } 194 } 195 196 @CddTest(requirement="7.6.1") 197 @Test testMinimumDownload()198 public void testMinimumDownload() throws Exception { 199 final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver(); 200 try { 201 IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); 202 mContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED); 203 204 long id = mDownloadManager.enqueue(new Request(getMinimumDownloadUrl())); 205 receiver.waitForDownloadComplete(LONG_TIMEOUT, id); 206 207 ParcelFileDescriptor fileDescriptor = mDownloadManager.openDownloadedFile(id); 208 assertEquals(MINIMUM_DOWNLOAD_BYTES, fileDescriptor.getStatSize()); 209 210 Cursor cursor = null; 211 try { 212 cursor = mDownloadManager.query(new Query().setFilterById(id)); 213 assertTrue(cursor.moveToNext()); 214 assertEquals(DownloadManager.STATUS_SUCCESSFUL, cursor.getInt( 215 cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))); 216 assertEquals(MINIMUM_DOWNLOAD_BYTES, cursor.getInt( 217 cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))); 218 assertFalse(cursor.moveToNext()); 219 } finally { 220 if (cursor != null) { 221 cursor.close(); 222 } 223 } 224 225 assertRemoveDownload(id, 0); 226 } finally { 227 mContext.unregisterReceiver(receiver); 228 } 229 } 230 231 /** 232 * Set download locations and verify that file is downloaded to correct location. 233 * 234 * Checks three different methods of setting location: directly via setDestinationUri, and 235 * indirectly through setDestinationInExternalFilesDir and setDestinationinExternalPublicDir. 236 */ 237 @FlakyTest 238 @Test testDownloadManagerDestination()239 public void testDownloadManagerDestination() throws Exception { 240 File uriLocation = new File(mContext.getExternalFilesDir(null), "uriFile.bin"); 241 deleteFromShell(uriLocation); 242 243 File extFileLocation = new File(mContext.getExternalFilesDir(null), "extFile.bin"); 244 deleteFromShell(extFileLocation); 245 246 File publicLocation = new File( 247 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), 248 "publicFile.bin"); 249 deleteFromShell(publicLocation); 250 251 final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver(); 252 try { 253 IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); 254 mContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED); 255 256 Request requestUri = new Request(getGoodUrl()); 257 requestUri.setDestinationUri(Uri.fromFile(uriLocation)); 258 long uriId = mDownloadManager.enqueue(requestUri); 259 260 Request requestExtFile = new Request(getGoodUrl()); 261 requestExtFile.setDestinationInExternalFilesDir(mContext, null, "extFile.bin"); 262 long extFileId = mDownloadManager.enqueue(requestExtFile); 263 264 Request requestPublic = new Request(getGoodUrl()); 265 requestPublic.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, 266 "publicFile.bin"); 267 long publicId = mDownloadManager.enqueue(requestPublic); 268 269 int allDownloads = getTotalNumberDownloads(); 270 assertEquals(3, allDownloads); 271 272 receiver.waitForDownloadComplete(SHORT_TIMEOUT, uriId, extFileId, publicId); 273 274 assertSuccessfulDownload(uriId, uriLocation); 275 assertSuccessfulDownload(extFileId, extFileLocation); 276 assertSuccessfulDownload(publicId, publicLocation); 277 278 assertRemoveDownload(uriId, allDownloads - 1); 279 assertRemoveDownload(extFileId, allDownloads - 2); 280 assertRemoveDownload(publicId, allDownloads - 3); 281 } finally { 282 mContext.unregisterReceiver(receiver); 283 } 284 } 285 286 /** 287 * Set the download location and verify that the extension of the file name is left unchanged. 288 */ 289 @Test testDownloadManagerDestinationExtension()290 public void testDownloadManagerDestinationExtension() throws Exception { 291 String noExt = "noiseandchirps"; 292 File noExtLocation = new File(mContext.getExternalFilesDir(null), noExt); 293 deleteFromShell(noExtLocation); 294 295 String wrongExt = "noiseandchirps.wrong"; 296 File wrongExtLocation = new File(mContext.getExternalFilesDir(null), wrongExt); 297 deleteFromShell(wrongExtLocation); 298 299 final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver(); 300 try { 301 IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); 302 mContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED); 303 304 Request requestNoExt = new Request(getAssetUrl(noExt)); 305 requestNoExt.setDestinationUri(Uri.fromFile(noExtLocation)); 306 long noExtId = mDownloadManager.enqueue(requestNoExt); 307 308 Request requestWrongExt = new Request(getAssetUrl(wrongExt)); 309 requestWrongExt.setDestinationUri(Uri.fromFile(wrongExtLocation)); 310 long wrongExtId = mDownloadManager.enqueue(requestWrongExt); 311 312 int allDownloads = getTotalNumberDownloads(); 313 assertEquals(2, allDownloads); 314 315 receiver.waitForDownloadComplete(SHORT_TIMEOUT, noExtId, wrongExtId); 316 317 assertSuccessfulDownload(noExtId, noExtLocation); 318 assertSuccessfulDownload(wrongExtId, wrongExtLocation); 319 320 assertRemoveDownload(noExtId, allDownloads - 1); 321 assertRemoveDownload(wrongExtId, allDownloads - 2); 322 } finally { 323 mContext.unregisterReceiver(receiver); 324 } 325 } 326 327 @Test testSetDestinationUri_privateAppDir()328 public void testSetDestinationUri_privateAppDir() throws Exception { 329 // Make sure the private app directory exists 330 runShellCommand("mkdir -p /sdcard/Android/data/com.android.shell -m 2770"); 331 final File path = new File("/sdcard/Android/data/com.android.shell/" 332 + TAG + System.currentTimeMillis()); 333 334 final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver(); 335 try { 336 IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); 337 mContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED); 338 339 DownloadManager.Request requestPublic = new DownloadManager.Request(getGoodUrl()); 340 requestPublic.setDestinationUri(Uri.fromFile(path)); 341 mDownloadManager.enqueue(requestPublic); 342 Assert.fail("Cannot download files into other app's private directories"); 343 } catch (SecurityException expected) { 344 } finally { 345 mContext.unregisterReceiver(receiver); 346 } 347 } 348 349 @Test testSetDestinationUri_invalidRequests()350 public void testSetDestinationUri_invalidRequests() throws Exception { 351 final File documentsFile = new File( 352 Environment.getExternalStoragePublicDirectory("TestDir"), 353 "uriFile.bin"); 354 deleteFromShell(documentsFile); 355 356 final Request badRequest = new Request(getGoodUrl()); 357 badRequest.setDestinationUri(Uri.fromFile(documentsFile)); 358 try { 359 mDownloadManager.enqueue(badRequest); 360 fail(documentsFile + " is not valid for setDestinationUri()"); 361 } catch (Exception e) { 362 // Expected 363 } 364 365 final Uri badUri = Uri.parse("file:///sdcard/Android/data/" 366 + mContext.getPackageName() + "/../uriFile.bin"); 367 final Request badRequest2 = new Request(getGoodUrl()); 368 badRequest2.setDestinationUri(badUri); 369 try { 370 mDownloadManager.enqueue(badRequest2); 371 fail(badUri + " is not valid for setDestinationUri()"); 372 } catch (Exception e) { 373 // Expected 374 } 375 376 final File sdcardPath = new File("/sdcard/uriFile.bin"); 377 final Request badRequest3 = new Request(getGoodUrl()); 378 badRequest3.setDestinationUri(Uri.fromFile(sdcardPath)); 379 try { 380 mDownloadManager.enqueue(badRequest2); 381 fail(sdcardPath + " is not valid for setDestinationUri()"); 382 } catch (Exception e) { 383 // Expected 384 } 385 } 386 387 @Test testSetDestinationUri()388 public void testSetDestinationUri() throws Exception { 389 final File[] destinationFiles = { 390 new File(Environment.getExternalStoragePublicDirectory( 391 Environment.DIRECTORY_DOWNLOADS), System.nanoTime() + "_uriFile.bin"), 392 new File(Environment.getExternalStoragePublicDirectory( 393 Environment.DIRECTORY_DOWNLOADS), System.nanoTime() + "_file.jpg"), 394 }; 395 396 for (File downloadsFile : destinationFiles) { 397 final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver(); 398 try { 399 IntentFilter intentFilter = new IntentFilter( 400 DownloadManager.ACTION_DOWNLOAD_COMPLETE); 401 mContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED); 402 403 DownloadManager.Request request = new DownloadManager.Request(getGoodUrl()); 404 request.setDestinationUri(Uri.fromFile(downloadsFile)); 405 long id = mDownloadManager.enqueue(request); 406 407 int allDownloads = getTotalNumberDownloads(); 408 assertEquals(1, allDownloads); 409 410 receiver.waitForDownloadComplete(SHORT_TIMEOUT, id); 411 assertSuccessfulDownload(id, downloadsFile); 412 413 assertRemoveDownload(id, 0); 414 } finally { 415 mContext.unregisterReceiver(receiver); 416 } 417 } 418 } 419 420 @Test testSetDestinationInExternalPublicDownloadDir()421 public void testSetDestinationInExternalPublicDownloadDir() throws Exception { 422 final String[] destinations = { 423 Environment.DIRECTORY_DOWNLOADS, 424 Environment.DIRECTORY_DCIM, 425 }; 426 final String[] subPaths = { 427 "testing/" + System.nanoTime() + "_publicFile.bin", 428 System.nanoTime() + "_file.jpg", 429 }; 430 431 for (int i = 0; i < destinations.length; ++i) { 432 final String destination = destinations[i]; 433 final String subPath = subPaths[i]; 434 435 final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver(); 436 try { 437 IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); 438 mContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED); 439 440 DownloadManager.Request requestPublic = new DownloadManager.Request(getGoodUrl()); 441 requestPublic.setDestinationInExternalPublicDir(destination, subPath); 442 long id = mDownloadManager.enqueue(requestPublic); 443 444 int allDownloads = getTotalNumberDownloads(); 445 assertEquals(1, allDownloads); 446 447 receiver.waitForDownloadComplete(SHORT_TIMEOUT, id); 448 assertSuccessfulDownload(id, new File( 449 Environment.getExternalStoragePublicDirectory(destination), subPath)); 450 451 assertRemoveDownload(id, 0); 452 } finally { 453 mContext.unregisterReceiver(receiver); 454 } 455 } 456 } 457 458 @Test testSetDestinationInExternalPublicDir_invalidRequests()459 public void testSetDestinationInExternalPublicDir_invalidRequests() { 460 final Request badRequest2 = new Request(getGoodUrl()); 461 try { 462 badRequest2.setDestinationInExternalPublicDir("TestDir", "uriFile.bin"); 463 mDownloadManager.enqueue(badRequest2); 464 fail("/sdcard/TestDir/uriFile.bin" 465 + " is not valid for setDestinationInExternalPublicDir()"); 466 } catch (Exception e) { 467 // Expected 468 } 469 } 470 canonicalizeProcessName(ApplicationInfo ai)471 private String canonicalizeProcessName(ApplicationInfo ai) { 472 return canonicalizeProcessName(ai.processName, ai); 473 } 474 canonicalizeProcessName(String process, ApplicationInfo ai)475 private String canonicalizeProcessName(String process, ApplicationInfo ai) { 476 if (process == null) { 477 return null; 478 } 479 // Handle private scoped process names. 480 if (process.startsWith(":")) { 481 return ai.packageName + process; 482 } 483 return process; 484 } 485 getExternalVolumeMediaStoreFilesCount()486 private int getExternalVolumeMediaStoreFilesCount() { 487 Uri rootUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL); 488 String[] projection = {MediaStore.MediaColumns._ID}; 489 int count = 0; 490 try (Cursor cursor = mContext.getContentResolver().query(rootUri, projection, null, null)) { 491 count = cursor.getCount(); 492 } 493 return count; 494 } 495 496 @FlakyTest 497 @Test testProviderAcceptsCleartext()498 public void testProviderAcceptsCleartext() throws Exception { 499 // Check that all the applications that share an android:process with the DownloadProvider 500 // accept cleartext traffic. Otherwise process loading races can lead to inconsistent flags. 501 final PackageManager pm = mContext.getPackageManager(); 502 ProviderInfo downloadInfo = pm.resolveContentProvider("downloads", 0); 503 assertNotNull(downloadInfo); 504 String downloadProcess 505 = canonicalizeProcessName(downloadInfo.processName, downloadInfo.applicationInfo); 506 507 for (PackageInfo pi : mContext.getPackageManager().getInstalledPackages(0)) { 508 if (downloadProcess.equals(canonicalizeProcessName(pi.applicationInfo))) { 509 assertTrue("package: " + pi.applicationInfo.packageName 510 + " must set android:usesCleartextTraffic=true" 511 ,(pi.applicationInfo.flags & ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC) 512 != 0); 513 } 514 } 515 } 516 517 /** 518 * Test that a download marked as not visible in Downloads ui can be successfully downloaded. 519 */ 520 @Test testDownloadNotVisibleInUi()521 public void testDownloadNotVisibleInUi() throws Exception { 522 File uriLocation = new File(mContext.getExternalFilesDir(null), "uriFile.bin"); 523 deleteFromShell(uriLocation); 524 525 final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver(); 526 try { 527 IntentFilter intentFilter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE); 528 mContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED); 529 530 final Request request = new Request(getGoodUrl()); 531 request.setDestinationUri(Uri.fromFile(uriLocation)) 532 .setVisibleInDownloadsUi(false); 533 long uriId = mDownloadManager.enqueue(request); 534 535 int allDownloads = getTotalNumberDownloads(); 536 assertEquals(1, allDownloads); 537 538 receiver.waitForDownloadComplete(SHORT_TIMEOUT, uriId); 539 540 assertSuccessfulDownload(uriId, uriLocation); 541 542 assertRemoveDownload(uriId, 0); 543 } finally { 544 mContext.unregisterReceiver(receiver); 545 } 546 } 547 548 //TODO(b/130797842): Remove FlakyTest annotation after that bug is fixed. 549 @FlakyTest 550 @Test testAddCompletedDownload()551 public void testAddCompletedDownload() throws Exception { 552 final String fileContents = "RED;GREEN;BLUE"; 553 final File file = createFile(mContext.getExternalFilesDir(null), "colors.txt"); 554 555 writeToFile(file, fileContents); 556 557 final long id = mDownloadManager.addCompletedDownload(file.getName(), "Test desc", true, 558 "text/plain", file.getPath(), fileContents.getBytes().length, true); 559 final String actualContents = readFromFile(mDownloadManager.openDownloadedFile(id)); 560 assertEquals(fileContents, actualContents); 561 562 final Uri downloadUri = mDownloadManager.getUriForDownloadedFile(id); 563 mContext.grantUriPermission("com.android.shell", downloadUri, 564 Intent.FLAG_GRANT_READ_URI_PERMISSION); 565 final String rawFilePath = getRawFilePath(downloadUri); 566 final String rawFileContents = readFromRawFile(rawFilePath); 567 assertEquals(fileContents, rawFileContents); 568 assertRemoveDownload(id, 0); 569 } 570 571 @Test testAddCompletedDownload_downloadDir()572 public void testAddCompletedDownload_downloadDir() throws Exception { 573 final String path = createFile(Environment.getExternalStoragePublicDirectory( 574 Environment.DIRECTORY_DOWNLOADS), "file1.txt").getPath(); 575 576 final String fileContents = "Test content:" + path + "_" + System.nanoTime(); 577 578 final File file = new File(path); 579 writeToFile(new File(path), fileContents); 580 581 final long id = mDownloadManager.addCompletedDownload(file.getName(), "Test desc", true, 582 "text/plain", path, fileContents.getBytes().length, true); 583 final String actualContents = readFromFile(mDownloadManager.openDownloadedFile(id)); 584 assertEquals(fileContents, actualContents); 585 586 final Uri downloadUri = mDownloadManager.getUriForDownloadedFile(id); 587 mContext.grantUriPermission("com.android.shell", downloadUri, 588 Intent.FLAG_GRANT_READ_URI_PERMISSION); 589 final String rawFilePath = getRawFilePath(downloadUri); 590 final String rawFileContents = readFromRawFile(rawFilePath); 591 assertEquals(fileContents, rawFileContents); 592 assertRemoveDownload(id, 0); 593 } 594 595 @Test testAddCompletedDownload_invalidPaths()596 public void testAddCompletedDownload_invalidPaths() throws Exception { 597 final String fileContents = "RED;GREEN;BLUE"; 598 599 // Try adding internal path 600 File file = createFile(mContext.getFilesDir(), "colors.txt"); 601 writeToFile(file, fileContents); 602 try { 603 mDownloadManager.addCompletedDownload("Test title", "Test desc", true, 604 "text/plain", file.getPath(), fileContents.getBytes().length, true); 605 fail("addCompletedDownload should have failed for adding internal path"); 606 } catch (Exception e) { 607 // expected 608 } 609 610 // Try adding path in top-level documents dir 611 file = new File( 612 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS), 613 "colors.txt"); 614 writeToFileWithDelegator(file, fileContents); 615 try { 616 mDownloadManager.addCompletedDownload("Test title", "Test desc", true, 617 "text/plain", file.getPath(), fileContents.getBytes().length, true); 618 fail("addCompletedDownload should have failed for top-level download dir"); 619 } catch (SecurityException e) { 620 // expected 621 } 622 623 // Try adding top-level sdcard path 624 final String path = "/sdcard/test-download.txt"; 625 writeToFileWithDelegator(new File(path), fileContents); 626 try { 627 mDownloadManager.addCompletedDownload("Test title", "Test desc", true, 628 "text/plain", path, fileContents.getBytes().length, true); 629 fail("addCompletedDownload should have failed for top-level sdcard path"); 630 } catch (SecurityException e) { 631 // expected 632 } 633 634 635 final String path2 = "/sdcard/Android/data/" + mContext.getPackageName() 636 + "/../uriFile.bin"; 637 try { 638 mDownloadManager.addCompletedDownload("Test title", "Test desc", true, 639 "text/plain", path2, fileContents.getBytes().length, true); 640 fail(path2 + " is not valid for addCompleteDownload()"); 641 } catch (Exception e) { 642 // Expected 643 } 644 645 // Try adding non-existent path 646 try { 647 mDownloadManager.addCompletedDownload("Test title", "Test desc", true, "text/plain", 648 new File(mContext.getExternalFilesDir(null), "test_file.mp4").getPath(), 649 fileContents.getBytes().length, true); 650 fail("addCompletedDownload should have failed for adding non-existent path"); 651 } catch (Exception e) { 652 // expected 653 } 654 655 // Try adding random string 656 try { 657 mDownloadManager.addCompletedDownload("Test title", "Test desc", true, 658 "text/plain", "RANDOM", fileContents.getBytes().length, true); 659 fail("addCompletedDownload should have failed for adding random string"); 660 } catch (Exception e) { 661 // expected 662 } 663 } 664 665 @Test testDownload_mediaScanned()666 public void testDownload_mediaScanned() throws Exception { 667 final String[] destinations = { 668 Environment.DIRECTORY_MUSIC, 669 Environment.DIRECTORY_DOWNLOADS, 670 }; 671 final String[] subPaths = { 672 "testmp3.mp3", 673 "testvideo.3gp", 674 }; 675 final Pair<String, String>[] expectedMediaAttributes = new Pair[] { 676 Pair.create(MediaStore.Audio.AudioColumns.IS_MUSIC, "1"), 677 Pair.create(MediaStore.Video.VideoColumns.DURATION, "11047"), 678 }; 679 680 for (int i = 0; i < destinations.length; ++i) { 681 final String destination = destinations[i]; 682 final String subPath = subPaths[i]; 683 684 final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver(); 685 try { 686 IntentFilter intentFilter = new IntentFilter( 687 DownloadManager.ACTION_DOWNLOAD_COMPLETE); 688 mContext.registerReceiver(receiver, intentFilter, Context.RECEIVER_EXPORTED); 689 690 DownloadManager.Request requestPublic = new DownloadManager.Request( 691 getAssetUrl(subPath)); 692 requestPublic.setDestinationInExternalPublicDir(destination, subPath); 693 long id = mDownloadManager.enqueue(requestPublic); 694 695 int allDownloads = getTotalNumberDownloads(); 696 assertEquals(1, allDownloads); 697 698 int countBeforeDownload = getExternalVolumeMediaStoreFilesCount(); 699 receiver.waitForDownloadComplete(SHORT_TIMEOUT, id); 700 assertSuccessfulDownload(id, new File( 701 Environment.getExternalStoragePublicDirectory(destination), subPath)); 702 703 int countAfterDownload = getExternalVolumeMediaStoreFilesCount(); 704 // Asserts that only one row entry is added for 1 download 705 assertEquals((countBeforeDownload + 1), countAfterDownload); 706 final Uri downloadUri = mDownloadManager.getUriForDownloadedFile(id); 707 mContext.grantUriPermission("com.android.shell", downloadUri, 708 Intent.FLAG_GRANT_READ_URI_PERMISSION); 709 final Uri mediaStoreUri = getMediaStoreUri(downloadUri); 710 assertEquals(expectedMediaAttributes[i].second, 711 getMediaStoreColumnValue(mediaStoreUri, expectedMediaAttributes[i].first)); 712 final int expectedSize = getTotalBytes( 713 mContext.getContentResolver().openInputStream(downloadUri)); 714 assertEquals(expectedSize, Integer.parseInt(getMediaStoreColumnValue( 715 mediaStoreUri, MediaStore.MediaColumns.SIZE))); 716 717 assertRemoveDownload(id, 0); 718 } finally { 719 mContext.unregisterReceiver(receiver); 720 } 721 } 722 } 723 724 @Test testDownload_onMediaStoreDownloadsDeleted()725 public void testDownload_onMediaStoreDownloadsDeleted() throws Exception { 726 assumeDocumentsUiAvailableOnFormFactor(); 727 728 // prepare file 729 File file = new File(Environment.getExternalStoragePublicDirectory( 730 Environment.DIRECTORY_DOWNLOADS), "cts" + System.nanoTime() + ".mp3"); 731 try { 732 stageFile("testmp3.mp3", file); 733 Uri mediaStoreUri = MediaStore.scanFile(mContext.getContentResolver(), file); 734 735 // setup for activity 736 GetResultActivity activity = setUpForActivity(); 737 738 // call activity as we want uri permission grant on DownloadStorageProvider Uri 739 final Intent intent = new Intent(); 740 intent.setAction(Intent.ACTION_OPEN_DOCUMENT); 741 intent.addCategory(Intent.CATEGORY_OPENABLE); 742 intent.setType("*/*"); 743 Uri rootUri = DocumentsContract.buildRootUri(DOWNLOAD_STORAGE_PROVIDER_AUTHORITY, 744 "downloads"); 745 intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, rootUri); 746 activity.startActivityForResult(intent, REQUEST_CODE); 747 mDevice.waitForIdle(); 748 749 findDocument(file.getName()).click(); 750 final GetResultActivity.Result result = activity.getResult(); 751 final Uri uri = result.data.getData(); 752 753 assertTrue(PERMISSION_GRANTED == checkUriPermission(uri)); 754 755 // onMediaStoreDownloadsDeleted API is called on MediaProvider#delete() 756 // for download files. This API revokes grants on DownloadStorageProvider granted Uris 757 LongSparseArray<String> lpa = new LongSparseArray<String>(); 758 final long mediaStoreId = Long.parseLong(getMediaStoreColumnValue(mediaStoreUri, 759 MediaStore.Files.FileColumns._ID)); 760 final String mimeType = getMediaStoreColumnValue(mediaStoreUri, 761 MediaStore.Files.FileColumns.MIME_TYPE); 762 lpa.put(mediaStoreId, mimeType); 763 // This API is permission protected 764 mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(WRITE_MEDIA_STORAGE); 765 try { 766 mDownloadManager.onMediaStoreDownloadsDeleted(lpa); 767 } finally { 768 mInstrumentation.getUiAutomation().dropShellPermissionIdentity(); 769 } 770 771 assertTrue(PERMISSION_DENIED == checkUriPermission(uri)); 772 } finally { 773 file.delete(); 774 } 775 } 776 setUpForActivity()777 private GetResultActivity setUpForActivity() { 778 final PackageManager pm = mContext.getPackageManager(); 779 final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); 780 intent.addCategory(Intent.CATEGORY_OPENABLE); 781 intent.setType("*/*"); 782 final ResolveInfo ri = pm.resolveActivity(intent, 0); 783 mDocumentsUiPackageId = ri.activityInfo.packageName; 784 785 final Intent intent2 = new Intent(mContext, GetResultActivity.class); 786 intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 787 GetResultActivity activity = (GetResultActivity) mInstrumentation.startActivitySync( 788 intent2); 789 mInstrumentation.waitForIdleSync(); 790 activity.clearResult(); 791 return activity; 792 } 793 checkUriPermission(Uri uri)794 private int checkUriPermission(Uri uri) { 795 return mContext.checkUriPermission(uri, android.os.Process.myPid(), 796 android.os.Process.myUid(), Intent.FLAG_GRANT_READ_URI_PERMISSION); 797 } 798 findDocument(String label)799 private UiObject findDocument(String label) throws UiObjectNotFoundException { 800 final UiSelector docList = new UiSelector().resourceId(getDocumentsUiPackageId() 801 + ":id/dir_list"); 802 803 // Wait for the first list item to appear 804 assertTrue("First list item", 805 new UiObject(docList.childSelector(new UiSelector())) 806 .waitForExists(LONG_TIMEOUT)); 807 808 try { 809 //Enforce to set the list mode 810 //Because UiScrollable can't reach the real bottom (when WEB_LINKABLE_FILE item) 811 // in grid mode when screen landscape mode 812 new UiObject(new UiSelector().resourceId(getDocumentsUiPackageId() 813 + ":id/sub_menu_list")).click(); 814 mDevice.waitForIdle(); 815 } catch (UiObjectNotFoundException e) { 816 //do nothing, already be in list mode. 817 } 818 819 // Repeat swipe gesture to find our item 820 // (UiScrollable#scrollIntoView does not seem to work well with SwipeRefreshLayout) 821 UiObject targetObject = new UiObject(docList.childSelector(new UiSelector().text(label))); 822 UiObject saveButton = findSaveButton(); 823 int stepLimit = 10; 824 while (stepLimit-- > 0) { 825 if (targetObject.exists()) { 826 boolean targetObjectFullyVisible = !saveButton.exists() 827 || targetObject.getVisibleBounds().bottom 828 <= saveButton.getVisibleBounds().top; 829 if (targetObjectFullyVisible) { 830 break; 831 } 832 } 833 834 mDevice.swipe(/* startX= */ mDevice.getDisplayWidth() / 2, 835 /* startY= */ mDevice.getDisplayHeight() / 2, 836 /* endX= */ mDevice.getDisplayWidth() / 2, 837 /* endY= */ 0, 838 /* steps= */ 40); 839 } 840 return targetObject; 841 } 842 findSaveButton()843 private UiObject findSaveButton() throws UiObjectNotFoundException { 844 return new UiObject(new UiSelector().resourceId( 845 getDocumentsUiPackageId() + ":id/container_save") 846 .childSelector(new UiSelector().resourceId("android:id/button1"))); 847 } 848 getDocumentsUiPackageId()849 private String getDocumentsUiPackageId() { 850 return mDocumentsUiPackageId; 851 } 852 stageFile(String resId, File file)853 private File stageFile(String resId, File file) throws IOException { 854 // The caller may be trying to stage into a location only available to 855 // the shell user, so we need to perform the entire copy as the shell 856 final File dir = file.getParentFile(); 857 dir.mkdirs(); 858 if (!dir.exists()) { 859 throw new FileNotFoundException("Failed to create parent for " + file); 860 } 861 try (InputStream source = mContext.getAssets().open(resId); 862 OutputStream target = new FileOutputStream(file)) { 863 FileUtils.copy(source, target); 864 } 865 return file; 866 } 867 } 868