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