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