1 /* 2 * Copyright (C) 2010 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.app; 18 19 import android.annotation.Nullable; 20 import android.annotation.SdkConstant; 21 import android.annotation.SdkConstant.SdkConstantType; 22 import android.annotation.SystemApi; 23 import android.annotation.SystemService; 24 import android.annotation.TestApi; 25 import android.compat.annotation.UnsupportedAppUsage; 26 import android.content.ContentProviderClient; 27 import android.content.ContentResolver; 28 import android.content.ContentUris; 29 import android.content.ContentValues; 30 import android.content.Context; 31 import android.database.Cursor; 32 import android.database.CursorWrapper; 33 import android.database.DatabaseUtils; 34 import android.net.ConnectivityManager; 35 import android.net.NetworkPolicyManager; 36 import android.net.Uri; 37 import android.os.Build; 38 import android.os.Bundle; 39 import android.os.Environment; 40 import android.os.FileUtils; 41 import android.os.ParcelFileDescriptor; 42 import android.os.RemoteException; 43 import android.provider.Downloads; 44 import android.provider.MediaStore; 45 import android.provider.Settings; 46 import android.provider.Settings.SettingNotFoundException; 47 import android.text.TextUtils; 48 import android.util.Pair; 49 50 import java.io.File; 51 import java.io.FileNotFoundException; 52 import java.util.ArrayList; 53 import java.util.List; 54 55 /** 56 * The download manager is a system service that handles long-running HTTP downloads. Clients may 57 * request that a URI be downloaded to a particular destination file. The download manager will 58 * conduct the download in the background, taking care of HTTP interactions and retrying downloads 59 * after failures or across connectivity changes and system reboots. 60 * <p> 61 * Apps that request downloads through this API should register a broadcast receiver for 62 * {@link #ACTION_NOTIFICATION_CLICKED} to appropriately handle when the user clicks on a running 63 * download in a notification or from the downloads UI. 64 * <p> 65 * Note that the application must have the {@link android.Manifest.permission#INTERNET} 66 * permission to use this class. 67 */ 68 @SystemService(Context.DOWNLOAD_SERVICE) 69 public class DownloadManager { 70 71 /** 72 * An identifier for a particular download, unique across the system. Clients use this ID to 73 * make subsequent calls related to the download. 74 */ 75 public final static String COLUMN_ID = Downloads.Impl._ID; 76 77 /** 78 * The client-supplied title for this download. This will be displayed in system notifications. 79 * Defaults to the empty string. 80 */ 81 public final static String COLUMN_TITLE = Downloads.Impl.COLUMN_TITLE; 82 83 /** 84 * The client-supplied description of this download. This will be displayed in system 85 * notifications. Defaults to the empty string. 86 */ 87 public final static String COLUMN_DESCRIPTION = Downloads.Impl.COLUMN_DESCRIPTION; 88 89 /** 90 * URI to be downloaded. 91 */ 92 public final static String COLUMN_URI = Downloads.Impl.COLUMN_URI; 93 94 /** 95 * Internet Media Type of the downloaded file. If no value is provided upon creation, this will 96 * initially be null and will be filled in based on the server's response once the download has 97 * started. 98 * 99 * @see <a href="http://www.ietf.org/rfc/rfc1590.txt">RFC 1590, defining Media Types</a> 100 */ 101 public final static String COLUMN_MEDIA_TYPE = "media_type"; 102 103 /** 104 * Total size of the download in bytes. This will initially be -1 and will be filled in once 105 * the download starts. 106 */ 107 public final static String COLUMN_TOTAL_SIZE_BYTES = "total_size"; 108 109 /** 110 * Uri where downloaded file will be stored. If a destination is supplied by client, that URI 111 * will be used here. Otherwise, the value will initially be null and will be filled in with a 112 * generated URI once the download has started. 113 */ 114 public final static String COLUMN_LOCAL_URI = "local_uri"; 115 116 /** 117 * Path to the downloaded file on disk. 118 * <p> 119 * Note that apps may not have filesystem permissions to directly access 120 * this path. Instead of trying to open this path directly, apps should use 121 * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain access. 122 * 123 * @deprecated apps should transition to using 124 * {@link ContentResolver#openFileDescriptor(Uri, String)} 125 * instead. 126 */ 127 @Deprecated 128 public final static String COLUMN_LOCAL_FILENAME = "local_filename"; 129 130 /** 131 * Current status of the download, as one of the STATUS_* constants. 132 */ 133 public final static String COLUMN_STATUS = Downloads.Impl.COLUMN_STATUS; 134 135 /** {@hide} */ 136 public static final String COLUMN_FILE_NAME_HINT = Downloads.Impl.COLUMN_FILE_NAME_HINT; 137 138 /** 139 * Provides more detail on the status of the download. Its meaning depends on the value of 140 * {@link #COLUMN_STATUS}. 141 * 142 * When {@link #COLUMN_STATUS} is {@link #STATUS_FAILED}, this indicates the type of error that 143 * occurred. If an HTTP error occurred, this will hold the HTTP status code as defined in RFC 144 * 2616. Otherwise, it will hold one of the ERROR_* constants. 145 * 146 * When {@link #COLUMN_STATUS} is {@link #STATUS_PAUSED}, this indicates why the download is 147 * paused. It will hold one of the PAUSED_* constants. 148 * 149 * If {@link #COLUMN_STATUS} is neither {@link #STATUS_FAILED} nor {@link #STATUS_PAUSED}, this 150 * column's value is undefined. 151 * 152 * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1">RFC 2616 153 * status codes</a> 154 */ 155 public final static String COLUMN_REASON = "reason"; 156 157 /** 158 * Number of bytes download so far. 159 */ 160 public final static String COLUMN_BYTES_DOWNLOADED_SO_FAR = "bytes_so_far"; 161 162 /** 163 * Timestamp when the download was last modified, in {@link System#currentTimeMillis 164 * System.currentTimeMillis()} (wall clock time in UTC). 165 */ 166 public final static String COLUMN_LAST_MODIFIED_TIMESTAMP = "last_modified_timestamp"; 167 168 /** 169 * The URI to the corresponding entry in MediaProvider for this downloaded entry. It is 170 * used to delete the entries from MediaProvider database when it is deleted from the 171 * downloaded list. 172 */ 173 public static final String COLUMN_MEDIAPROVIDER_URI = Downloads.Impl.COLUMN_MEDIAPROVIDER_URI; 174 175 /** @hide */ 176 @TestApi 177 public static final String COLUMN_MEDIASTORE_URI = Downloads.Impl.COLUMN_MEDIASTORE_URI; 178 179 /** {@hide} */ 180 public static final String COLUMN_DESTINATION = Downloads.Impl.COLUMN_DESTINATION; 181 182 /** 183 * @hide 184 */ 185 public final static String COLUMN_ALLOW_WRITE = Downloads.Impl.COLUMN_ALLOW_WRITE; 186 187 /** 188 * Value of {@link #COLUMN_STATUS} when the download is waiting to start. 189 */ 190 public final static int STATUS_PENDING = 1 << 0; 191 192 /** 193 * Value of {@link #COLUMN_STATUS} when the download is currently running. 194 */ 195 public final static int STATUS_RUNNING = 1 << 1; 196 197 /** 198 * Value of {@link #COLUMN_STATUS} when the download is waiting to retry or resume. 199 */ 200 public final static int STATUS_PAUSED = 1 << 2; 201 202 /** 203 * Value of {@link #COLUMN_STATUS} when the download has successfully completed. 204 */ 205 public final static int STATUS_SUCCESSFUL = 1 << 3; 206 207 /** 208 * Value of {@link #COLUMN_STATUS} when the download has failed (and will not be retried). 209 */ 210 public final static int STATUS_FAILED = 1 << 4; 211 212 /** 213 * Value of COLUMN_ERROR_CODE when the download has completed with an error that doesn't fit 214 * under any other error code. 215 */ 216 public final static int ERROR_UNKNOWN = 1000; 217 218 /** 219 * Value of {@link #COLUMN_REASON} when a storage issue arises which doesn't fit under any 220 * other error code. Use the more specific {@link #ERROR_INSUFFICIENT_SPACE} and 221 * {@link #ERROR_DEVICE_NOT_FOUND} when appropriate. 222 */ 223 public final static int ERROR_FILE_ERROR = 1001; 224 225 /** 226 * Value of {@link #COLUMN_REASON} when an HTTP code was received that download manager 227 * can't handle. 228 */ 229 public final static int ERROR_UNHANDLED_HTTP_CODE = 1002; 230 231 /** 232 * Value of {@link #COLUMN_REASON} when an error receiving or processing data occurred at 233 * the HTTP level. 234 */ 235 public final static int ERROR_HTTP_DATA_ERROR = 1004; 236 237 /** 238 * Value of {@link #COLUMN_REASON} when there were too many redirects. 239 */ 240 public final static int ERROR_TOO_MANY_REDIRECTS = 1005; 241 242 /** 243 * Value of {@link #COLUMN_REASON} when there was insufficient storage space. Typically, 244 * this is because the SD card is full. 245 */ 246 public final static int ERROR_INSUFFICIENT_SPACE = 1006; 247 248 /** 249 * Value of {@link #COLUMN_REASON} when no external storage device was found. Typically, 250 * this is because the SD card is not mounted. 251 */ 252 public final static int ERROR_DEVICE_NOT_FOUND = 1007; 253 254 /** 255 * Value of {@link #COLUMN_REASON} when some possibly transient error occurred but we can't 256 * resume the download. 257 */ 258 public final static int ERROR_CANNOT_RESUME = 1008; 259 260 /** 261 * Value of {@link #COLUMN_REASON} when the requested destination file already exists (the 262 * download manager will not overwrite an existing file). 263 */ 264 public final static int ERROR_FILE_ALREADY_EXISTS = 1009; 265 266 /** 267 * Value of {@link #COLUMN_REASON} when the download has failed because of 268 * {@link NetworkPolicyManager} controls on the requesting application. 269 * 270 * @hide 271 */ 272 public final static int ERROR_BLOCKED = 1010; 273 274 /** 275 * Value of {@link #COLUMN_REASON} when the download is paused because some network error 276 * occurred and the download manager is waiting before retrying the request. 277 */ 278 public final static int PAUSED_WAITING_TO_RETRY = 1; 279 280 /** 281 * Value of {@link #COLUMN_REASON} when the download is waiting for network connectivity to 282 * proceed. 283 */ 284 public final static int PAUSED_WAITING_FOR_NETWORK = 2; 285 286 /** 287 * Value of {@link #COLUMN_REASON} when the download exceeds a size limit for downloads over 288 * the mobile network and the download manager is waiting for a Wi-Fi connection to proceed. 289 */ 290 public final static int PAUSED_QUEUED_FOR_WIFI = 3; 291 292 /** 293 * Value of {@link #COLUMN_REASON} when the download is paused for some other reason. 294 */ 295 public final static int PAUSED_UNKNOWN = 4; 296 297 /** 298 * Broadcast intent action sent by the download manager when a download completes. 299 */ 300 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 301 public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE"; 302 303 /** 304 * Broadcast intent action sent by the download manager when the user clicks on a running 305 * download, either from a system notification or from the downloads UI. 306 */ 307 @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) 308 public final static String ACTION_NOTIFICATION_CLICKED = 309 "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED"; 310 311 /** 312 * Intent action to launch an activity to display all downloads. 313 */ 314 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 315 public final static String ACTION_VIEW_DOWNLOADS = "android.intent.action.VIEW_DOWNLOADS"; 316 317 /** 318 * Intent extra included with {@link #ACTION_VIEW_DOWNLOADS} to start DownloadApp in 319 * sort-by-size mode. 320 */ 321 public final static String INTENT_EXTRAS_SORT_BY_SIZE = 322 "android.app.DownloadManager.extra_sortBySize"; 323 324 /** 325 * Intent extra included with {@link #ACTION_DOWNLOAD_COMPLETE} intents, indicating the ID (as a 326 * long) of the download that just completed. 327 */ 328 public static final String EXTRA_DOWNLOAD_ID = "extra_download_id"; 329 330 /** 331 * When clicks on multiple notifications are received, the following 332 * provides an array of download ids corresponding to the download notification that was 333 * clicked. It can be retrieved by the receiver of this 334 * Intent using {@link android.content.Intent#getLongArrayExtra(String)}. 335 */ 336 public static final String EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS = "extra_click_download_ids"; 337 338 /** {@hide} */ 339 @SystemApi 340 public static final String ACTION_DOWNLOAD_COMPLETED = 341 "android.intent.action.DOWNLOAD_COMPLETED"; 342 343 /** 344 * columns to request from DownloadProvider. 345 * @hide 346 */ 347 @UnsupportedAppUsage 348 public static final String[] UNDERLYING_COLUMNS = new String[] { 349 DownloadManager.COLUMN_ID, 350 DownloadManager.COLUMN_LOCAL_FILENAME, 351 DownloadManager.COLUMN_MEDIAPROVIDER_URI, 352 DownloadManager.COLUMN_DESTINATION, 353 DownloadManager.COLUMN_TITLE, 354 DownloadManager.COLUMN_DESCRIPTION, 355 DownloadManager.COLUMN_URI, 356 DownloadManager.COLUMN_STATUS, 357 DownloadManager.COLUMN_FILE_NAME_HINT, 358 DownloadManager.COLUMN_MEDIA_TYPE, 359 DownloadManager.COLUMN_TOTAL_SIZE_BYTES, 360 DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP, 361 DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR, 362 DownloadManager.COLUMN_ALLOW_WRITE, 363 DownloadManager.COLUMN_LOCAL_URI, 364 DownloadManager.COLUMN_REASON 365 }; 366 367 /** 368 * This class contains all the information necessary to request a new download. The URI is the 369 * only required parameter. 370 * 371 * Note that the default download destination is a shared volume where the system might delete 372 * your file if it needs to reclaim space for system use. If this is a problem, use a location 373 * on external storage (see {@link #setDestinationUri(Uri)}. 374 */ 375 public static class Request { 376 /** 377 * Bit flag for {@link #setAllowedNetworkTypes} corresponding to 378 * {@link ConnectivityManager#TYPE_MOBILE}. 379 */ 380 public static final int NETWORK_MOBILE = 1 << 0; 381 382 /** 383 * Bit flag for {@link #setAllowedNetworkTypes} corresponding to 384 * {@link ConnectivityManager#TYPE_WIFI}. 385 */ 386 public static final int NETWORK_WIFI = 1 << 1; 387 388 /** 389 * Bit flag for {@link #setAllowedNetworkTypes} corresponding to 390 * {@link ConnectivityManager#TYPE_BLUETOOTH}. 391 * @hide 392 */ 393 @Deprecated 394 public static final int NETWORK_BLUETOOTH = 1 << 2; 395 396 @UnsupportedAppUsage 397 private Uri mUri; 398 private Uri mDestinationUri; 399 private List<Pair<String, String>> mRequestHeaders = new ArrayList<Pair<String, String>>(); 400 private CharSequence mTitle; 401 private CharSequence mDescription; 402 private String mMimeType; 403 private int mAllowedNetworkTypes = ~0; // default to all network types allowed 404 private boolean mRoamingAllowed = true; 405 private boolean mMeteredAllowed = true; 406 private int mFlags = 0; 407 private boolean mIsVisibleInDownloadsUi = true; 408 private boolean mScannable = false; 409 /** if a file is designated as a MediaScanner scannable file, the following value is 410 * stored in the database column {@link Downloads.Impl#COLUMN_MEDIA_SCANNED}. 411 */ 412 private static final int SCANNABLE_VALUE_YES = Downloads.Impl.MEDIA_NOT_SCANNED; 413 // value of 1 is stored in the above column by DownloadProvider after it is scanned by 414 // MediaScanner 415 /** if a file is designated as a file that should not be scanned by MediaScanner, 416 * the following value is stored in the database column 417 * {@link Downloads.Impl#COLUMN_MEDIA_SCANNED}. 418 */ 419 private static final int SCANNABLE_VALUE_NO = Downloads.Impl.MEDIA_NOT_SCANNABLE; 420 421 /** 422 * This download is visible but only shows in the notifications 423 * while it's in progress. 424 */ 425 public static final int VISIBILITY_VISIBLE = 0; 426 427 /** 428 * This download is visible and shows in the notifications while 429 * in progress and after completion. 430 */ 431 public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1; 432 433 /** 434 * This download doesn't show in the UI or in the notifications. 435 */ 436 public static final int VISIBILITY_HIDDEN = 2; 437 438 /** 439 * This download shows in the notifications after completion ONLY. 440 * It is usuable only with 441 * {@link DownloadManager#addCompletedDownload(String, String, 442 * boolean, String, String, long, boolean)}. 443 */ 444 public static final int VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION = 3; 445 446 /** can take any of the following values: {@link #VISIBILITY_HIDDEN} 447 * {@link #VISIBILITY_VISIBLE_NOTIFY_COMPLETED}, {@link #VISIBILITY_VISIBLE}, 448 * {@link #VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION} 449 */ 450 private int mNotificationVisibility = VISIBILITY_VISIBLE; 451 452 /** 453 * @param uri the HTTP or HTTPS URI to download. 454 */ Request(Uri uri)455 public Request(Uri uri) { 456 if (uri == null) { 457 throw new NullPointerException(); 458 } 459 String scheme = uri.getScheme(); 460 if (scheme == null || (!scheme.equals("http") && !scheme.equals("https"))) { 461 throw new IllegalArgumentException("Can only download HTTP/HTTPS URIs: " + uri); 462 } 463 mUri = uri; 464 } 465 Request(String uriString)466 Request(String uriString) { 467 mUri = Uri.parse(uriString); 468 } 469 470 /** 471 * Set the local destination for the downloaded file. Must be a file URI to a path on 472 * external storage, and the calling application must have the WRITE_EXTERNAL_STORAGE 473 * permission. 474 * <p> 475 * The downloaded file is not scanned by MediaScanner. 476 * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}. 477 * <p> 478 * By default, downloads are saved to a generated filename in the shared download cache and 479 * may be deleted by the system at any time to reclaim space. 480 * 481 * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above, 482 * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE WRITE EXTERNAL_STORAGE} 483 * permission is not needed and the {@code uri} must refer to a path within the 484 * directories owned by the application (e.g. {@link Context#getExternalFilesDir(String)}) 485 * or a path within the top-level Downloads directory (as returned by 486 * {@link Environment#getExternalStoragePublicDirectory(String)} with 487 * {@link Environment#DIRECTORY_DOWNLOADS}). 488 * 489 * @param uri a file {@link Uri} indicating the destination for the downloaded file. 490 * @return this object 491 */ setDestinationUri(Uri uri)492 public Request setDestinationUri(Uri uri) { 493 mDestinationUri = uri; 494 return this; 495 } 496 497 /** 498 * Set the local destination for the downloaded file to a path within 499 * the application's external files directory (as returned by 500 * {@link Context#getExternalFilesDir(String)}. 501 * <p> 502 * The downloaded file is not scanned by MediaScanner. But it can be 503 * made scannable by calling {@link #allowScanningByMediaScanner()}. 504 * 505 * @param context the {@link Context} to use in determining the external 506 * files directory 507 * @param dirType the directory type to pass to 508 * {@link Context#getExternalFilesDir(String)} 509 * @param subPath the path within the external directory, including the 510 * destination filename 511 * @return this object 512 * @throws IllegalStateException If the external storage directory 513 * cannot be found or created. 514 */ setDestinationInExternalFilesDir(Context context, String dirType, String subPath)515 public Request setDestinationInExternalFilesDir(Context context, String dirType, 516 String subPath) { 517 final File file = context.getExternalFilesDir(dirType); 518 if (file == null) { 519 throw new IllegalStateException("Failed to get external storage files directory"); 520 } else if (file.exists()) { 521 if (!file.isDirectory()) { 522 throw new IllegalStateException(file.getAbsolutePath() + 523 " already exists and is not a directory"); 524 } 525 } else { 526 if (!file.mkdirs()) { 527 throw new IllegalStateException("Unable to create directory: "+ 528 file.getAbsolutePath()); 529 } 530 } 531 setDestinationFromBase(file, subPath); 532 return this; 533 } 534 535 /** 536 * Set the local destination for the downloaded file to a path within 537 * the public external storage directory (as returned by 538 * {@link Environment#getExternalStoragePublicDirectory(String)}). 539 * <p> 540 * The downloaded file is not scanned by MediaScanner. But it can be 541 * made scannable by calling {@link #allowScanningByMediaScanner()}. 542 * 543 * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above, 544 * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE} 545 * permission is not needed and the {@code dirType} must be one of the known public 546 * directories like {@link Environment#DIRECTORY_DOWNLOADS}, 547 * {@link Environment#DIRECTORY_PICTURES}, {@link Environment#DIRECTORY_MOVIES}, etc. 548 * 549 * @param dirType the directory type to pass to {@link Environment#getExternalStoragePublicDirectory(String)} 550 * @param subPath the path within the external directory, including the 551 * destination filename 552 * @return this object 553 * @throws IllegalStateException If the external storage directory 554 * cannot be found or created. 555 */ setDestinationInExternalPublicDir(String dirType, String subPath)556 public Request setDestinationInExternalPublicDir(String dirType, String subPath) { 557 File file = Environment.getExternalStoragePublicDirectory(dirType); 558 if (file == null) { 559 throw new IllegalStateException("Failed to get external storage public directory"); 560 } 561 562 final Context context = AppGlobals.getInitialApplication(); 563 if (context.getApplicationInfo().targetSdkVersion 564 >= Build.VERSION_CODES.Q || !Environment.isExternalStorageLegacy()) { 565 try (ContentProviderClient client = context.getContentResolver() 566 .acquireContentProviderClient(Downloads.Impl.AUTHORITY)) { 567 final Bundle extras = new Bundle(); 568 extras.putString(Downloads.DIR_TYPE, dirType); 569 client.call(Downloads.CALL_CREATE_EXTERNAL_PUBLIC_DIR, null, extras); 570 } catch (RemoteException e) { 571 throw new IllegalStateException("Unable to create directory: " 572 + file.getAbsolutePath()); 573 } 574 } else { 575 if (file.exists()) { 576 if (!file.isDirectory()) { 577 throw new IllegalStateException(file.getAbsolutePath() 578 + " already exists and is not a directory"); 579 } 580 } else if (!file.mkdirs()) { 581 throw new IllegalStateException("Unable to create directory: " 582 + file.getAbsolutePath()); 583 } 584 } 585 setDestinationFromBase(file, subPath); 586 return this; 587 } 588 setDestinationFromBase(File base, String subPath)589 private void setDestinationFromBase(File base, String subPath) { 590 if (subPath == null) { 591 throw new NullPointerException("subPath cannot be null"); 592 } 593 mDestinationUri = Uri.withAppendedPath(Uri.fromFile(base), subPath); 594 } 595 596 /** 597 * If the file to be downloaded is to be scanned by MediaScanner, this method 598 * should be called before {@link DownloadManager#enqueue(Request)} is called. 599 * 600 * @deprecated Starting in Q, this value is ignored. Files downloaded to 601 * directories owned by applications (e.g. {@link Context#getExternalFilesDir(String)}) 602 * will not be scanned by MediaScanner and the rest will be scanned. 603 */ 604 @Deprecated allowScanningByMediaScanner()605 public void allowScanningByMediaScanner() { 606 mScannable = true; 607 } 608 609 /** 610 * Add an HTTP header to be included with the download request. The header will be added to 611 * the end of the list. 612 * @param header HTTP header name 613 * @param value header value 614 * @return this object 615 * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">HTTP/1.1 616 * Message Headers</a> 617 */ addRequestHeader(String header, String value)618 public Request addRequestHeader(String header, String value) { 619 if (header == null) { 620 throw new NullPointerException("header cannot be null"); 621 } 622 if (header.contains(":")) { 623 throw new IllegalArgumentException("header may not contain ':'"); 624 } 625 if (value == null) { 626 value = ""; 627 } 628 mRequestHeaders.add(Pair.create(header, value)); 629 return this; 630 } 631 632 /** 633 * Set the title of this download, to be displayed in notifications (if enabled). If no 634 * title is given, a default one will be assigned based on the download filename, once the 635 * download starts. 636 * @return this object 637 */ setTitle(CharSequence title)638 public Request setTitle(CharSequence title) { 639 mTitle = title; 640 return this; 641 } 642 643 /** 644 * Set a description of this download, to be displayed in notifications (if enabled) 645 * @return this object 646 */ setDescription(CharSequence description)647 public Request setDescription(CharSequence description) { 648 mDescription = description; 649 return this; 650 } 651 652 /** 653 * Set the MIME content type of this download. This will override the content type declared 654 * in the server's response. 655 * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7">HTTP/1.1 656 * Media Types</a> 657 * @return this object 658 */ setMimeType(String mimeType)659 public Request setMimeType(String mimeType) { 660 mMimeType = mimeType; 661 return this; 662 } 663 664 /** 665 * Control whether a system notification is posted by the download manager while this 666 * download is running. If enabled, the download manager posts notifications about downloads 667 * through the system {@link android.app.NotificationManager}. By default, a notification is 668 * shown. 669 * 670 * If set to false, this requires the permission 671 * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION. 672 * 673 * @param show whether the download manager should show a notification for this download. 674 * @return this object 675 * @deprecated use {@link #setNotificationVisibility(int)} 676 */ 677 @Deprecated setShowRunningNotification(boolean show)678 public Request setShowRunningNotification(boolean show) { 679 return (show) ? setNotificationVisibility(VISIBILITY_VISIBLE) : 680 setNotificationVisibility(VISIBILITY_HIDDEN); 681 } 682 683 /** 684 * Control whether a system notification is posted by the download manager while this 685 * download is running or when it is completed. 686 * If enabled, the download manager posts notifications about downloads 687 * through the system {@link android.app.NotificationManager}. 688 * By default, a notification is shown only when the download is in progress. 689 *<p> 690 * It can take the following values: {@link #VISIBILITY_HIDDEN}, 691 * {@link #VISIBILITY_VISIBLE}, 692 * {@link #VISIBILITY_VISIBLE_NOTIFY_COMPLETED}. 693 *<p> 694 * If set to {@link #VISIBILITY_HIDDEN}, this requires the permission 695 * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION. 696 * 697 * @param visibility the visibility setting value 698 * @return this object 699 */ setNotificationVisibility(int visibility)700 public Request setNotificationVisibility(int visibility) { 701 mNotificationVisibility = visibility; 702 return this; 703 } 704 705 /** 706 * Restrict the types of networks over which this download may proceed. 707 * By default, all network types are allowed. Consider using 708 * {@link #setAllowedOverMetered(boolean)} instead, since it's more 709 * flexible. 710 * <p> 711 * As of {@link android.os.Build.VERSION_CODES#N}, setting only the 712 * {@link #NETWORK_WIFI} flag here is equivalent to calling 713 * {@link #setAllowedOverMetered(boolean)} with {@code false}. 714 * 715 * @param flags any combination of the NETWORK_* bit flags. 716 * @return this object 717 */ setAllowedNetworkTypes(int flags)718 public Request setAllowedNetworkTypes(int flags) { 719 mAllowedNetworkTypes = flags; 720 return this; 721 } 722 723 /** 724 * Set whether this download may proceed over a roaming connection. By default, roaming is 725 * allowed. 726 * @param allowed whether to allow a roaming connection to be used 727 * @return this object 728 */ setAllowedOverRoaming(boolean allowed)729 public Request setAllowedOverRoaming(boolean allowed) { 730 mRoamingAllowed = allowed; 731 return this; 732 } 733 734 /** 735 * Set whether this download may proceed over a metered network 736 * connection. By default, metered networks are allowed. 737 * 738 * @see ConnectivityManager#isActiveNetworkMetered() 739 */ setAllowedOverMetered(boolean allow)740 public Request setAllowedOverMetered(boolean allow) { 741 mMeteredAllowed = allow; 742 return this; 743 } 744 745 /** 746 * Specify that to run this download, the device needs to be plugged in. 747 * This defaults to false. 748 * 749 * @param requiresCharging Whether or not the device is plugged in. 750 * @see android.app.job.JobInfo.Builder#setRequiresCharging(boolean) 751 */ setRequiresCharging(boolean requiresCharging)752 public Request setRequiresCharging(boolean requiresCharging) { 753 if (requiresCharging) { 754 mFlags |= Downloads.Impl.FLAG_REQUIRES_CHARGING; 755 } else { 756 mFlags &= ~Downloads.Impl.FLAG_REQUIRES_CHARGING; 757 } 758 return this; 759 } 760 761 /** 762 * Specify that to run, the download needs the device to be in idle 763 * mode. This defaults to false. 764 * <p> 765 * Idle mode is a loose definition provided by the system, which means 766 * that the device is not in use, and has not been in use for some time. 767 * 768 * @param requiresDeviceIdle Whether or not the device need be within an 769 * idle maintenance window. 770 * @see android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean) 771 */ setRequiresDeviceIdle(boolean requiresDeviceIdle)772 public Request setRequiresDeviceIdle(boolean requiresDeviceIdle) { 773 if (requiresDeviceIdle) { 774 mFlags |= Downloads.Impl.FLAG_REQUIRES_DEVICE_IDLE; 775 } else { 776 mFlags &= ~Downloads.Impl.FLAG_REQUIRES_DEVICE_IDLE; 777 } 778 return this; 779 } 780 781 /** 782 * Set whether this download should be displayed in the system's Downloads UI. True by 783 * default. 784 * @param isVisible whether to display this download in the Downloads UI 785 * @return this object 786 * 787 * @deprecated Starting in Q, this value is ignored. Only files downloaded to 788 * public Downloads directory (as returned by 789 * {@link Environment#getExternalStoragePublicDirectory(String)} with 790 * {@link Environment#DIRECTORY_DOWNLOADS}) will be visible in system's Downloads UI 791 * and the rest will not be visible. 792 * (e.g. {@link Context#getExternalFilesDir(String)}) will not be visible. 793 */ 794 @Deprecated setVisibleInDownloadsUi(boolean isVisible)795 public Request setVisibleInDownloadsUi(boolean isVisible) { 796 mIsVisibleInDownloadsUi = isVisible; 797 return this; 798 } 799 800 /** 801 * @return ContentValues to be passed to DownloadProvider.insert() 802 */ toContentValues(String packageName)803 ContentValues toContentValues(String packageName) { 804 ContentValues values = new ContentValues(); 805 assert mUri != null; 806 values.put(Downloads.Impl.COLUMN_URI, mUri.toString()); 807 values.put(Downloads.Impl.COLUMN_IS_PUBLIC_API, true); 808 values.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, packageName); 809 810 if (mDestinationUri != null) { 811 values.put(Downloads.Impl.COLUMN_DESTINATION, 812 Downloads.Impl.DESTINATION_FILE_URI); 813 values.put(Downloads.Impl.COLUMN_FILE_NAME_HINT, 814 mDestinationUri.toString()); 815 } else { 816 values.put(Downloads.Impl.COLUMN_DESTINATION, 817 Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE); 818 } 819 // is the file supposed to be media-scannable? 820 values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED, (mScannable) ? SCANNABLE_VALUE_YES : 821 SCANNABLE_VALUE_NO); 822 823 if (!mRequestHeaders.isEmpty()) { 824 encodeHttpHeaders(values); 825 } 826 827 putIfNonNull(values, Downloads.Impl.COLUMN_TITLE, mTitle); 828 putIfNonNull(values, Downloads.Impl.COLUMN_DESCRIPTION, mDescription); 829 putIfNonNull(values, Downloads.Impl.COLUMN_MIME_TYPE, mMimeType); 830 831 values.put(Downloads.Impl.COLUMN_VISIBILITY, mNotificationVisibility); 832 values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes); 833 values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed); 834 values.put(Downloads.Impl.COLUMN_ALLOW_METERED, mMeteredAllowed); 835 values.put(Downloads.Impl.COLUMN_FLAGS, mFlags); 836 values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, mIsVisibleInDownloadsUi); 837 838 return values; 839 } 840 encodeHttpHeaders(ContentValues values)841 private void encodeHttpHeaders(ContentValues values) { 842 int index = 0; 843 for (Pair<String, String> header : mRequestHeaders) { 844 String headerString = header.first + ": " + header.second; 845 values.put(Downloads.Impl.RequestHeaders.INSERT_KEY_PREFIX + index, headerString); 846 index++; 847 } 848 } 849 putIfNonNull(ContentValues contentValues, String key, Object value)850 private void putIfNonNull(ContentValues contentValues, String key, Object value) { 851 if (value != null) { 852 contentValues.put(key, value.toString()); 853 } 854 } 855 } 856 857 /** 858 * This class may be used to filter download manager queries. 859 */ 860 public static class Query { 861 /** 862 * Constant for use with {@link #orderBy} 863 * @hide 864 */ 865 public static final int ORDER_ASCENDING = 1; 866 867 /** 868 * Constant for use with {@link #orderBy} 869 * @hide 870 */ 871 public static final int ORDER_DESCENDING = 2; 872 873 private long[] mIds = null; 874 private Integer mStatusFlags = null; 875 private String mFilterString = null; 876 private String mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION; 877 private int mOrderDirection = ORDER_DESCENDING; 878 private boolean mOnlyIncludeVisibleInDownloadsUi = false; 879 880 /** 881 * Include only the downloads with the given IDs. 882 * @return this object 883 */ setFilterById(long... ids)884 public Query setFilterById(long... ids) { 885 mIds = ids; 886 return this; 887 } 888 889 /** 890 * 891 * Include only the downloads that contains the given string in its name. 892 * @return this object 893 * @hide 894 */ setFilterByString(@ullable String filter)895 public Query setFilterByString(@Nullable String filter) { 896 mFilterString = filter; 897 return this; 898 } 899 900 /** 901 * Include only downloads with status matching any the given status flags. 902 * @param flags any combination of the STATUS_* bit flags 903 * @return this object 904 */ setFilterByStatus(int flags)905 public Query setFilterByStatus(int flags) { 906 mStatusFlags = flags; 907 return this; 908 } 909 910 /** 911 * Controls whether this query includes downloads not visible in the system's Downloads UI. 912 * @param value if true, this query will only include downloads that should be displayed in 913 * the system's Downloads UI; if false (the default), this query will include 914 * both visible and invisible downloads. 915 * @return this object 916 * @hide 917 */ 918 @UnsupportedAppUsage setOnlyIncludeVisibleInDownloadsUi(boolean value)919 public Query setOnlyIncludeVisibleInDownloadsUi(boolean value) { 920 mOnlyIncludeVisibleInDownloadsUi = value; 921 return this; 922 } 923 924 /** 925 * Change the sort order of the returned Cursor. 926 * 927 * @param column one of the COLUMN_* constants; currently, only 928 * {@link #COLUMN_LAST_MODIFIED_TIMESTAMP} and {@link #COLUMN_TOTAL_SIZE_BYTES} are 929 * supported. 930 * @param direction either {@link #ORDER_ASCENDING} or {@link #ORDER_DESCENDING} 931 * @return this object 932 * @hide 933 */ 934 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) orderBy(String column, int direction)935 public Query orderBy(String column, int direction) { 936 if (direction != ORDER_ASCENDING && direction != ORDER_DESCENDING) { 937 throw new IllegalArgumentException("Invalid direction: " + direction); 938 } 939 940 if (column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP)) { 941 mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION; 942 } else if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) { 943 mOrderByColumn = Downloads.Impl.COLUMN_TOTAL_BYTES; 944 } else { 945 throw new IllegalArgumentException("Cannot order by " + column); 946 } 947 mOrderDirection = direction; 948 return this; 949 } 950 951 /** 952 * Run this query using the given ContentResolver. 953 * @param projection the projection to pass to ContentResolver.query() 954 * @return the Cursor returned by ContentResolver.query() 955 */ runQuery(ContentResolver resolver, String[] projection, Uri baseUri)956 Cursor runQuery(ContentResolver resolver, String[] projection, Uri baseUri) { 957 Uri uri = baseUri; 958 List<String> selectionParts = new ArrayList<String>(); 959 String[] selectionArgs = null; 960 961 int whereArgsCount = (mIds == null) ? 0 : mIds.length; 962 whereArgsCount = (mFilterString == null) ? whereArgsCount : whereArgsCount + 1; 963 selectionArgs = new String[whereArgsCount]; 964 965 if (whereArgsCount > 0) { 966 if (mIds != null) { 967 selectionParts.add(getWhereClauseForIds(mIds)); 968 getWhereArgsForIds(mIds, selectionArgs); 969 } 970 971 if (mFilterString != null) { 972 selectionParts.add(Downloads.Impl.COLUMN_TITLE + " LIKE ?"); 973 selectionArgs[selectionArgs.length - 1] = "%" + mFilterString + "%"; 974 } 975 } 976 977 if (mStatusFlags != null) { 978 List<String> parts = new ArrayList<String>(); 979 if ((mStatusFlags & STATUS_PENDING) != 0) { 980 parts.add(statusClause("=", Downloads.Impl.STATUS_PENDING)); 981 } 982 if ((mStatusFlags & STATUS_RUNNING) != 0) { 983 parts.add(statusClause("=", Downloads.Impl.STATUS_RUNNING)); 984 } 985 if ((mStatusFlags & STATUS_PAUSED) != 0) { 986 parts.add(statusClause("=", Downloads.Impl.STATUS_PAUSED_BY_APP)); 987 parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_TO_RETRY)); 988 parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_FOR_NETWORK)); 989 parts.add(statusClause("=", Downloads.Impl.STATUS_QUEUED_FOR_WIFI)); 990 } 991 if ((mStatusFlags & STATUS_SUCCESSFUL) != 0) { 992 parts.add(statusClause("=", Downloads.Impl.STATUS_SUCCESS)); 993 } 994 if ((mStatusFlags & STATUS_FAILED) != 0) { 995 parts.add("(" + statusClause(">=", 400) 996 + " AND " + statusClause("<", 600) + ")"); 997 } 998 selectionParts.add(joinStrings(" OR ", parts)); 999 } 1000 1001 if (mOnlyIncludeVisibleInDownloadsUi) { 1002 selectionParts.add(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI + " != '0'"); 1003 } 1004 1005 // only return rows which are not marked 'deleted = 1' 1006 selectionParts.add(Downloads.Impl.COLUMN_DELETED + " != '1'"); 1007 1008 String selection = joinStrings(" AND ", selectionParts); 1009 String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC"); 1010 String orderBy = mOrderByColumn + " " + orderDirection; 1011 1012 return resolver.query(uri, projection, selection, selectionArgs, orderBy); 1013 } 1014 joinStrings(String joiner, Iterable<String> parts)1015 private String joinStrings(String joiner, Iterable<String> parts) { 1016 StringBuilder builder = new StringBuilder(); 1017 boolean first = true; 1018 for (String part : parts) { 1019 if (!first) { 1020 builder.append(joiner); 1021 } 1022 builder.append(part); 1023 first = false; 1024 } 1025 return builder.toString(); 1026 } 1027 statusClause(String operator, int value)1028 private String statusClause(String operator, int value) { 1029 return Downloads.Impl.COLUMN_STATUS + operator + "'" + value + "'"; 1030 } 1031 } 1032 1033 private final ContentResolver mResolver; 1034 private final String mPackageName; 1035 1036 private Uri mBaseUri = Downloads.Impl.CONTENT_URI; 1037 private boolean mAccessFilename; 1038 1039 /** 1040 * @hide 1041 */ DownloadManager(Context context)1042 public DownloadManager(Context context) { 1043 mResolver = context.getContentResolver(); 1044 mPackageName = context.getPackageName(); 1045 1046 // Callers can access filename columns when targeting old platform 1047 // versions; otherwise we throw telling them it's deprecated. 1048 mAccessFilename = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N; 1049 } 1050 1051 /** 1052 * Makes this object access the download provider through /all_downloads URIs rather than 1053 * /my_downloads URIs, for clients that have permission to do so. 1054 * @hide 1055 */ 1056 @UnsupportedAppUsage 1057 public void setAccessAllDownloads(boolean accessAllDownloads) { 1058 if (accessAllDownloads) { 1059 mBaseUri = Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI; 1060 } else { 1061 mBaseUri = Downloads.Impl.CONTENT_URI; 1062 } 1063 } 1064 1065 /** {@hide} */ 1066 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 1067 public void setAccessFilename(boolean accessFilename) { 1068 mAccessFilename = accessFilename; 1069 } 1070 1071 /** 1072 * Enqueue a new download. The download will start automatically once the download manager is 1073 * ready to execute it and connectivity is available. 1074 * 1075 * @param request the parameters specifying this download 1076 * @return an ID for the download, unique across the system. This ID is used to make future 1077 * calls related to this download. 1078 */ 1079 public long enqueue(Request request) { 1080 ContentValues values = request.toContentValues(mPackageName); 1081 Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values); 1082 long id = Long.parseLong(downloadUri.getLastPathSegment()); 1083 return id; 1084 } 1085 1086 /** 1087 * Marks the specified download as 'to be deleted'. This is done when a completed download 1088 * is to be removed but the row was stored without enough info to delete the corresponding 1089 * metadata from Mediaprovider database. Actual cleanup of this row is done in DownloadService. 1090 * 1091 * @param ids the IDs of the downloads to be marked 'deleted' 1092 * @return the number of downloads actually updated 1093 * @hide 1094 */ 1095 public int markRowDeleted(long... ids) { 1096 if (ids == null || ids.length == 0) { 1097 // called with nothing to remove! 1098 throw new IllegalArgumentException("input param 'ids' can't be null"); 1099 } 1100 return mResolver.delete(mBaseUri, getWhereClauseForIds(ids), getWhereArgsForIds(ids)); 1101 } 1102 1103 /** 1104 * Cancel downloads and remove them from the download manager. Each download will be stopped if 1105 * it was running, and it will no longer be accessible through the download manager. 1106 * If there is a downloaded file, partial or complete, it is deleted. 1107 * 1108 * @param ids the IDs of the downloads to remove 1109 * @return the number of downloads actually removed 1110 */ 1111 public int remove(long... ids) { 1112 return markRowDeleted(ids); 1113 } 1114 1115 /** 1116 * Query the download manager about downloads that have been requested. 1117 * @param query parameters specifying filters for this query 1118 * @return a Cursor over the result set of downloads, with columns consisting of all the 1119 * COLUMN_* constants. 1120 */ 1121 public Cursor query(Query query) { 1122 return query(query, UNDERLYING_COLUMNS); 1123 } 1124 1125 /** @hide */ 1126 public Cursor query(Query query, String[] projection) { 1127 Cursor underlyingCursor = query.runQuery(mResolver, projection, mBaseUri); 1128 if (underlyingCursor == null) { 1129 return null; 1130 } 1131 return new CursorTranslator(underlyingCursor, mBaseUri, mAccessFilename); 1132 } 1133 1134 /** 1135 * Open a downloaded file for reading. The download must have completed. 1136 * @param id the ID of the download 1137 * @return a read-only {@link ParcelFileDescriptor} 1138 * @throws FileNotFoundException if the destination file does not already exist 1139 */ 1140 public ParcelFileDescriptor openDownloadedFile(long id) throws FileNotFoundException { 1141 return mResolver.openFileDescriptor(getDownloadUri(id), "r"); 1142 } 1143 1144 /** 1145 * Returns the {@link Uri} of the given downloaded file id, if the file is 1146 * downloaded successfully. Otherwise, null is returned. 1147 * 1148 * @param id the id of the downloaded file. 1149 * @return the {@link Uri} of the given downloaded file id, if download was 1150 * successful. null otherwise. 1151 */ 1152 public Uri getUriForDownloadedFile(long id) { 1153 // to check if the file is in cache, get its destination from the database 1154 Query query = new Query().setFilterById(id); 1155 Cursor cursor = null; 1156 try { 1157 cursor = query(query); 1158 if (cursor == null) { 1159 return null; 1160 } 1161 if (cursor.moveToFirst()) { 1162 int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS)); 1163 if (DownloadManager.STATUS_SUCCESSFUL == status) { 1164 return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id); 1165 } 1166 } 1167 } finally { 1168 if (cursor != null) { 1169 cursor.close(); 1170 } 1171 } 1172 // downloaded file not found or its status is not 'successfully completed' 1173 return null; 1174 } 1175 1176 /** 1177 * Returns the media type of the given downloaded file id, if the file was 1178 * downloaded successfully. Otherwise, null is returned. 1179 * 1180 * @param id the id of the downloaded file. 1181 * @return the media type of the given downloaded file id, if download was successful. null 1182 * otherwise. 1183 */ 1184 public String getMimeTypeForDownloadedFile(long id) { 1185 Query query = new Query().setFilterById(id); 1186 Cursor cursor = null; 1187 try { 1188 cursor = query(query); 1189 if (cursor == null) { 1190 return null; 1191 } 1192 while (cursor.moveToFirst()) { 1193 return cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_MEDIA_TYPE)); 1194 } 1195 } finally { 1196 if (cursor != null) { 1197 cursor.close(); 1198 } 1199 } 1200 // downloaded file not found or its status is not 'successfully completed' 1201 return null; 1202 } 1203 1204 /** 1205 * Restart the given downloads, which must have already completed (successfully or not). This 1206 * method will only work when called from within the download manager's process. 1207 * @param ids the IDs of the downloads 1208 * @hide 1209 */ 1210 @UnsupportedAppUsage 1211 public void restartDownload(long... ids) { 1212 Cursor cursor = query(new Query().setFilterById(ids)); 1213 try { 1214 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 1215 int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS)); 1216 if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) { 1217 throw new IllegalArgumentException("Cannot restart incomplete download: " 1218 + cursor.getLong(cursor.getColumnIndex(COLUMN_ID))); 1219 } 1220 } 1221 } finally { 1222 cursor.close(); 1223 } 1224 1225 ContentValues values = new ContentValues(); 1226 values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0); 1227 values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1); 1228 values.putNull(Downloads.Impl._DATA); 1229 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING); 1230 values.put(Downloads.Impl.COLUMN_FAILED_CONNECTIONS, 0); 1231 mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids)); 1232 } 1233 1234 /** 1235 * Force the given downloads to proceed even if their size is larger than 1236 * {@link #getMaxBytesOverMobile(Context)}. 1237 * 1238 * @hide 1239 */ 1240 public void forceDownload(long... ids) { 1241 ContentValues values = new ContentValues(); 1242 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING); 1243 values.put(Downloads.Impl.COLUMN_CONTROL, Downloads.Impl.CONTROL_RUN); 1244 values.put(Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT, 1); 1245 mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids)); 1246 } 1247 1248 /** 1249 * Returns maximum size, in bytes, of downloads that may go over a mobile connection; or null if 1250 * there's no limit 1251 * 1252 * @param context the {@link Context} to use for accessing the {@link ContentResolver} 1253 * @return maximum size, in bytes, of downloads that may go over a mobile connection; or null if 1254 * there's no limit 1255 */ 1256 public static Long getMaxBytesOverMobile(Context context) { 1257 try { 1258 return Settings.Global.getLong(context.getContentResolver(), 1259 Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE); 1260 } catch (SettingNotFoundException exc) { 1261 return null; 1262 } 1263 } 1264 1265 /** 1266 * Rename the given download if the download has completed 1267 * 1268 * @param context the {@link Context} to use in case need to update MediaProvider 1269 * @param id the downloaded id 1270 * @param displayName the new name to rename to 1271 * @return true if rename was successful, false otherwise 1272 * @hide 1273 */ 1274 public boolean rename(Context context, long id, String displayName) { 1275 if (!FileUtils.isValidFatFilename(displayName)) { 1276 throw new SecurityException(displayName + " is not a valid filename"); 1277 } 1278 1279 final String filePath; 1280 final Query query = new Query().setFilterById(id); 1281 try (Cursor cursor = query(query)) { 1282 if (cursor == null) { 1283 throw new IllegalStateException("Missing cursor for download id=" + id); 1284 } 1285 if (cursor.moveToFirst()) { 1286 final int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS)); 1287 if (status != DownloadManager.STATUS_SUCCESSFUL) { 1288 throw new IllegalStateException("Download is not completed yet: " 1289 + DatabaseUtils.dumpCurrentRowToString(cursor)); 1290 } 1291 filePath = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_LOCAL_FILENAME)); 1292 if (filePath == null) { 1293 throw new IllegalStateException("Download doesn't have a valid file path: " 1294 + DatabaseUtils.dumpCurrentRowToString(cursor)); 1295 } else if (!new File(filePath).exists()) { 1296 throw new IllegalStateException("Downloaded file doesn't exist anymore: " 1297 + DatabaseUtils.dumpCurrentRowToString(cursor)); 1298 } 1299 } else { 1300 throw new IllegalStateException("Missing download id=" + id); 1301 } 1302 } 1303 1304 final File before = new File(filePath); 1305 final File after = new File(before.getParentFile(), displayName); 1306 1307 if (after.exists()) { 1308 throw new IllegalStateException("File already exists: " + after); 1309 } 1310 if (!before.renameTo(after)) { 1311 throw new IllegalStateException( 1312 "Failed to rename file from " + before + " to " + after); 1313 } 1314 1315 // TODO: DownloadProvider.update() should take care of updating corresponding 1316 // MediaProvider entries. 1317 MediaStore.scanFile(context, before); 1318 MediaStore.scanFile(context, after); 1319 1320 final ContentValues values = new ContentValues(); 1321 values.put(Downloads.Impl.COLUMN_TITLE, displayName); 1322 values.put(Downloads.Impl._DATA, after.toString()); 1323 values.putNull(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI); 1324 final long[] ids = { id }; 1325 1326 return mResolver.update( 1327 mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids)) == 1; 1328 } 1329 1330 /** 1331 * Returns recommended maximum size, in bytes, of downloads that may go over a mobile 1332 * connection; or null if there's no recommended limit. The user will have the option to bypass 1333 * this limit. 1334 * 1335 * @param context the {@link Context} to use for accessing the {@link ContentResolver} 1336 * @return recommended maximum size, in bytes, of downloads that may go over a mobile 1337 * connection; or null if there's no recommended limit. 1338 */ 1339 public static Long getRecommendedMaxBytesOverMobile(Context context) { 1340 try { 1341 return Settings.Global.getLong(context.getContentResolver(), 1342 Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE); 1343 } catch (SettingNotFoundException exc) { 1344 return null; 1345 } 1346 } 1347 1348 /** {@hide} */ 1349 public static boolean isActiveNetworkExpensive(Context context) { 1350 // TODO: connect to NetworkPolicyManager 1351 return false; 1352 } 1353 1354 /** {@hide} */ 1355 public static long getActiveNetworkWarningBytes(Context context) { 1356 // TODO: connect to NetworkPolicyManager 1357 return -1; 1358 } 1359 1360 /** 1361 * Adds a file to the downloads database system, so it could appear in Downloads App 1362 * (and thus become eligible for management by the Downloads App). 1363 * <p> 1364 * It is helpful to make the file scannable by MediaScanner by setting the param 1365 * isMediaScannerScannable to true. It makes the file visible in media managing 1366 * applications such as Gallery App, which could be a useful purpose of using this API. 1367 * 1368 * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above, 1369 * {@code path} must be within directories owned by the application 1370 * {e.g. {@link Context#getExternalFilesDir(String)}} or if the application is running under 1371 * the legacy storage model (see 1372 * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage 1373 * android:requestLegacyExternalStorage}), {@code path} can also be within the top-level 1374 * Downloads directory (as returned by 1375 * {@link Environment#getExternalStoragePublicDirectory(String)} with 1376 * {@link Environment#DIRECTORY_DOWNLOADS}). 1377 * 1378 * @param title the title that would appear for this file in Downloads App. 1379 * @param description the description that would appear for this file in Downloads App. 1380 * @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files 1381 * scanned by MediaScanner appear in the applications used to view media (for example, 1382 * Gallery app). 1383 * @param mimeType mimetype of the file. 1384 * @param path absolute pathname to the file. The file should be world-readable, so that it can 1385 * be managed by the Downloads App and any other app that is used to read it (for example, 1386 * Gallery app to display the file, if the file contents represent a video/image). 1387 * @param length length of the downloaded file 1388 * @param showNotification true if a notification is to be sent, false otherwise 1389 * @return an ID for the download entry added to the downloads app, unique across the system 1390 * This ID is used to make future calls related to this download. 1391 * 1392 * @deprecated Apps should instead contribute files to 1393 * {@link android.provider.MediaStore.Downloads} collection to make them available to user 1394 * as part of Downloads. 1395 */ 1396 @Deprecated 1397 public long addCompletedDownload(String title, String description, 1398 boolean isMediaScannerScannable, String mimeType, String path, long length, 1399 boolean showNotification) { 1400 return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path, 1401 length, showNotification, false, null, null); 1402 } 1403 1404 /** 1405 * Adds a file to the downloads database system, so it could appear in Downloads App 1406 * (and thus become eligible for management by the Downloads App). 1407 * <p> 1408 * It is helpful to make the file scannable by MediaScanner by setting the param 1409 * isMediaScannerScannable to true. It makes the file visible in media managing 1410 * applications such as Gallery App, which could be a useful purpose of using this API. 1411 * 1412 * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above, 1413 * {@code path} must be within directories owned by the application 1414 * {e.g. {@link Context#getExternalFilesDir(String)}} or if the application is running under 1415 * the legacy storage model (see 1416 * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage 1417 * android:requestLegacyExternalStorage}), {@code path} can also be within the top-level 1418 * Downloads directory (as returned by 1419 * {@link Environment#getExternalStoragePublicDirectory(String)} with 1420 * {@link Environment#DIRECTORY_DOWNLOADS}). 1421 * 1422 * @param title the title that would appear for this file in Downloads App. 1423 * @param description the description that would appear for this file in Downloads App. 1424 * @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files 1425 * scanned by MediaScanner appear in the applications used to view media (for example, 1426 * Gallery app). 1427 * @param mimeType mimetype of the file. 1428 * @param path absolute pathname to the file. The file should be world-readable, so that it can 1429 * be managed by the Downloads App and any other app that is used to read it (for example, 1430 * Gallery app to display the file, if the file contents represent a video/image). 1431 * @param length length of the downloaded file 1432 * @param showNotification true if a notification is to be sent, false otherwise 1433 * @param uri the original HTTP URI of the download 1434 * @param referer the HTTP Referer for the download 1435 * @return an ID for the download entry added to the downloads app, unique across the system 1436 * This ID is used to make future calls related to this download. 1437 * 1438 * @deprecated Apps should instead contribute files to 1439 * {@link android.provider.MediaStore.Downloads} collection to make them available to user 1440 * as part of Downloads. 1441 */ 1442 @Deprecated 1443 public long addCompletedDownload(String title, String description, 1444 boolean isMediaScannerScannable, String mimeType, String path, long length, 1445 boolean showNotification, Uri uri, Uri referer) { 1446 return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path, 1447 length, showNotification, false, uri, referer); 1448 } 1449 1450 /** 1451 * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above, 1452 * {@code path} must be within directories owned by the application 1453 * {e.g. {@link Context#getExternalFilesDir(String)}} or if the application is running under 1454 * the legacy storage model (see 1455 * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage 1456 * android:requestLegacyExternalStorage}), {@code path} can also be within the top-level 1457 * Downloads directory (as returned by 1458 * {@link Environment#getExternalStoragePublicDirectory(String)} with 1459 * {@link Environment#DIRECTORY_DOWNLOADS}). 1460 * 1461 * @deprecated Apps should instead contribute files to 1462 * {@link android.provider.MediaStore.Downloads} collection to make them available to user 1463 * as part of Downloads. 1464 * 1465 * {@hide} 1466 */ 1467 @Deprecated 1468 public long addCompletedDownload(String title, String description, 1469 boolean isMediaScannerScannable, String mimeType, String path, long length, 1470 boolean showNotification, boolean allowWrite) { 1471 return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path, 1472 length, showNotification, allowWrite, null, null); 1473 } 1474 1475 /** 1476 * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above, 1477 * {@code path} must be within directories owned by the application 1478 * {e.g. {@link Context#getExternalFilesDir(String)}} or if the application is running under 1479 * the legacy storage model (see 1480 * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage 1481 * android:requestLegacyExternalStorage}), {@code path} can also be within the top-level 1482 * Downloads directory (as returned by 1483 * {@link Environment#getExternalStoragePublicDirectory(String)} with 1484 * {@link Environment#DIRECTORY_DOWNLOADS}). 1485 * 1486 * {@hide} 1487 * 1488 * @deprecated Apps should instead contribute files to 1489 * {@link android.provider.MediaStore.Downloads} collection to make them available to user 1490 * as part of Downloads. 1491 */ 1492 @Deprecated 1493 public long addCompletedDownload(String title, String description, 1494 boolean isMediaScannerScannable, String mimeType, String path, long length, 1495 boolean showNotification, boolean allowWrite, Uri uri, Uri referer) { 1496 // make sure the input args are non-null/non-zero 1497 validateArgumentIsNonEmpty("title", title); 1498 validateArgumentIsNonEmpty("description", description); 1499 validateArgumentIsNonEmpty("path", path); 1500 validateArgumentIsNonEmpty("mimeType", mimeType); 1501 if (length < 0) { 1502 throw new IllegalArgumentException(" invalid value for param: totalBytes"); 1503 } 1504 1505 // if there is already an entry with the given path name in downloads.db, return its id 1506 Request request; 1507 if (uri != null) { 1508 request = new Request(uri); 1509 } else { 1510 request = new Request(NON_DOWNLOADMANAGER_DOWNLOAD); 1511 } 1512 request.setTitle(title) 1513 .setDescription(description) 1514 .setMimeType(mimeType); 1515 if (referer != null) { 1516 request.addRequestHeader("Referer", referer.toString()); 1517 } 1518 ContentValues values = request.toContentValues(null); 1519 values.put(Downloads.Impl.COLUMN_DESTINATION, 1520 Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD); 1521 values.put(Downloads.Impl._DATA, path); 1522 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS); 1523 values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, length); 1524 values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED, 1525 (isMediaScannerScannable) ? Request.SCANNABLE_VALUE_YES : 1526 Request.SCANNABLE_VALUE_NO); 1527 values.put(Downloads.Impl.COLUMN_VISIBILITY, (showNotification) ? 1528 Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION : Request.VISIBILITY_HIDDEN); 1529 values.put(Downloads.Impl.COLUMN_ALLOW_WRITE, allowWrite ? 1 : 0); 1530 Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values); 1531 if (downloadUri == null) { 1532 return -1; 1533 } 1534 return Long.parseLong(downloadUri.getLastPathSegment()); 1535 } 1536 1537 private static final String NON_DOWNLOADMANAGER_DOWNLOAD = 1538 "non-dwnldmngr-download-dont-retry2download"; 1539 1540 private static void validateArgumentIsNonEmpty(String paramName, String val) { 1541 if (TextUtils.isEmpty(val)) { 1542 throw new IllegalArgumentException(paramName + " can't be null"); 1543 } 1544 } 1545 1546 /** 1547 * Get the DownloadProvider URI for the download with the given ID. 1548 * 1549 * @hide 1550 */ 1551 public Uri getDownloadUri(long id) { 1552 return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id); 1553 } 1554 1555 /** 1556 * Get a parameterized SQL WHERE clause to select a bunch of IDs. 1557 */ 1558 @UnsupportedAppUsage 1559 static String getWhereClauseForIds(long[] ids) { 1560 StringBuilder whereClause = new StringBuilder(); 1561 whereClause.append("("); 1562 for (int i = 0; i < ids.length; i++) { 1563 if (i > 0) { 1564 whereClause.append("OR "); 1565 } 1566 whereClause.append(Downloads.Impl._ID); 1567 whereClause.append(" = ? "); 1568 } 1569 whereClause.append(")"); 1570 return whereClause.toString(); 1571 } 1572 1573 /** 1574 * Get the selection args for a clause returned by {@link #getWhereClauseForIds(long[])}. 1575 */ 1576 @UnsupportedAppUsage 1577 static String[] getWhereArgsForIds(long[] ids) { 1578 String[] whereArgs = new String[ids.length]; 1579 return getWhereArgsForIds(ids, whereArgs); 1580 } 1581 1582 /** 1583 * Get selection args for a clause returned by {@link #getWhereClauseForIds(long[])} 1584 * and write it to the supplied args array. 1585 */ 1586 static String[] getWhereArgsForIds(long[] ids, String[] args) { 1587 assert(args.length >= ids.length); 1588 for (int i = 0; i < ids.length; i++) { 1589 args[i] = Long.toString(ids[i]); 1590 } 1591 return args; 1592 } 1593 1594 1595 /** 1596 * This class wraps a cursor returned by DownloadProvider -- the "underlying cursor" -- and 1597 * presents a different set of columns, those defined in the DownloadManager.COLUMN_* constants. 1598 * Some columns correspond directly to underlying values while others are computed from 1599 * underlying data. 1600 */ 1601 private static class CursorTranslator extends CursorWrapper { 1602 private final Uri mBaseUri; 1603 private final boolean mAccessFilename; 1604 1605 public CursorTranslator(Cursor cursor, Uri baseUri, boolean accessFilename) { 1606 super(cursor); 1607 mBaseUri = baseUri; 1608 mAccessFilename = accessFilename; 1609 } 1610 1611 @Override 1612 public int getInt(int columnIndex) { 1613 return (int) getLong(columnIndex); 1614 } 1615 1616 @Override 1617 public long getLong(int columnIndex) { 1618 if (getColumnName(columnIndex).equals(COLUMN_REASON)) { 1619 return getReason(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS))); 1620 } else if (getColumnName(columnIndex).equals(COLUMN_STATUS)) { 1621 return translateStatus(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS))); 1622 } else { 1623 return super.getLong(columnIndex); 1624 } 1625 } 1626 1627 @Override 1628 public String getString(int columnIndex) { 1629 final String columnName = getColumnName(columnIndex); 1630 switch (columnName) { 1631 case COLUMN_LOCAL_URI: 1632 return getLocalUri(); 1633 case COLUMN_LOCAL_FILENAME: 1634 if (!mAccessFilename) { 1635 throw new SecurityException( 1636 "COLUMN_LOCAL_FILENAME is deprecated;" 1637 + " use ContentResolver.openFileDescriptor() instead"); 1638 } 1639 default: 1640 return super.getString(columnIndex); 1641 } 1642 } 1643 1644 private String getLocalUri() { 1645 long destinationType = getLong(getColumnIndex(Downloads.Impl.COLUMN_DESTINATION)); 1646 if (destinationType == Downloads.Impl.DESTINATION_FILE_URI || 1647 destinationType == Downloads.Impl.DESTINATION_EXTERNAL || 1648 destinationType == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) { 1649 String localPath = super.getString(getColumnIndex(COLUMN_LOCAL_FILENAME)); 1650 if (localPath == null) { 1651 return null; 1652 } 1653 return Uri.fromFile(new File(localPath)).toString(); 1654 } 1655 1656 // return content URI for cache download 1657 long downloadId = getLong(getColumnIndex(Downloads.Impl._ID)); 1658 return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, downloadId).toString(); 1659 } 1660 1661 private long getReason(int status) { 1662 switch (translateStatus(status)) { 1663 case STATUS_FAILED: 1664 return getErrorCode(status); 1665 1666 case STATUS_PAUSED: 1667 return getPausedReason(status); 1668 1669 default: 1670 return 0; // arbitrary value when status is not an error 1671 } 1672 } 1673 1674 private long getPausedReason(int status) { 1675 switch (status) { 1676 case Downloads.Impl.STATUS_WAITING_TO_RETRY: 1677 return PAUSED_WAITING_TO_RETRY; 1678 1679 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK: 1680 return PAUSED_WAITING_FOR_NETWORK; 1681 1682 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI: 1683 return PAUSED_QUEUED_FOR_WIFI; 1684 1685 default: 1686 return PAUSED_UNKNOWN; 1687 } 1688 } 1689 1690 private long getErrorCode(int status) { 1691 if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS) 1692 || (500 <= status && status < 600)) { 1693 // HTTP status code 1694 return status; 1695 } 1696 1697 switch (status) { 1698 case Downloads.Impl.STATUS_FILE_ERROR: 1699 return ERROR_FILE_ERROR; 1700 1701 case Downloads.Impl.STATUS_UNHANDLED_HTTP_CODE: 1702 case Downloads.Impl.STATUS_UNHANDLED_REDIRECT: 1703 return ERROR_UNHANDLED_HTTP_CODE; 1704 1705 case Downloads.Impl.STATUS_HTTP_DATA_ERROR: 1706 return ERROR_HTTP_DATA_ERROR; 1707 1708 case Downloads.Impl.STATUS_TOO_MANY_REDIRECTS: 1709 return ERROR_TOO_MANY_REDIRECTS; 1710 1711 case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR: 1712 return ERROR_INSUFFICIENT_SPACE; 1713 1714 case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR: 1715 return ERROR_DEVICE_NOT_FOUND; 1716 1717 case Downloads.Impl.STATUS_CANNOT_RESUME: 1718 return ERROR_CANNOT_RESUME; 1719 1720 case Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR: 1721 return ERROR_FILE_ALREADY_EXISTS; 1722 1723 default: 1724 return ERROR_UNKNOWN; 1725 } 1726 } 1727 1728 private int translateStatus(int status) { 1729 switch (status) { 1730 case Downloads.Impl.STATUS_PENDING: 1731 return STATUS_PENDING; 1732 1733 case Downloads.Impl.STATUS_RUNNING: 1734 return STATUS_RUNNING; 1735 1736 case Downloads.Impl.STATUS_PAUSED_BY_APP: 1737 case Downloads.Impl.STATUS_WAITING_TO_RETRY: 1738 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK: 1739 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI: 1740 return STATUS_PAUSED; 1741 1742 case Downloads.Impl.STATUS_SUCCESS: 1743 return STATUS_SUCCESSFUL; 1744 1745 default: 1746 assert Downloads.Impl.isStatusError(status); 1747 return STATUS_FAILED; 1748 } 1749 } 1750 } 1751 } 1752