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