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