DownloadManager.java revision 98f030742c2889a0906226bd7eea914770b4e13b
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 17package android.app; 18 19import android.content.ContentResolver; 20import android.content.ContentUris; 21import android.content.ContentValues; 22import android.content.Context; 23import android.database.Cursor; 24import android.database.CursorWrapper; 25import android.net.ConnectivityManager; 26import android.net.Uri; 27import android.os.Environment; 28import android.os.ParcelFileDescriptor; 29import android.provider.Downloads; 30import android.util.Log; 31import android.util.Pair; 32 33import java.io.File; 34import java.io.FileNotFoundException; 35import java.util.ArrayList; 36import java.util.List; 37 38/** 39 * The download manager is a system service that handles long-running HTTP downloads. Clients may 40 * request that a URI be downloaded to a particular destination file. The download manager will 41 * conduct the download in the background, taking care of HTTP interactions and retrying downloads 42 * after failures or across connectivity changes and system reboots. 43 * 44 * Instances of this class should be obtained through 45 * {@link android.content.Context#getSystemService(String)} by passing 46 * {@link android.content.Context#DOWNLOAD_SERVICE}. 47 * 48 * Apps that request downloads through this API should register a broadcast receiver for 49 * {@link #ACTION_NOTIFICATION_CLICKED} to appropriately handle when the user clicks on a running 50 * download in a notification or from the downloads UI. 51 */ 52public class DownloadManager { 53 private static final String TAG = "DownloadManager"; 54 55 /** 56 * An identifier for a particular download, unique across the system. Clients use this ID to 57 * make subsequent calls related to the download. 58 */ 59 public final static String COLUMN_ID = Downloads.Impl._ID; 60 61 /** 62 * The client-supplied title for this download. This will be displayed in system notifications. 63 * Defaults to the empty string. 64 */ 65 public final static String COLUMN_TITLE = Downloads.Impl.COLUMN_TITLE; 66 67 /** 68 * The client-supplied description of this download. This will be displayed in system 69 * notifications. Defaults to the empty string. 70 */ 71 public final static String COLUMN_DESCRIPTION = Downloads.Impl.COLUMN_DESCRIPTION; 72 73 /** 74 * URI to be downloaded. 75 */ 76 public final static String COLUMN_URI = Downloads.Impl.COLUMN_URI; 77 78 /** 79 * Internet Media Type of the downloaded file. If no value is provided upon creation, this will 80 * initially be null and will be filled in based on the server's response once the download has 81 * started. 82 * 83 * @see <a href="http://www.ietf.org/rfc/rfc1590.txt">RFC 1590, defining Media Types</a> 84 */ 85 public final static String COLUMN_MEDIA_TYPE = "media_type"; 86 87 /** 88 * Total size of the download in bytes. This will initially be -1 and will be filled in once 89 * the download starts. 90 */ 91 public final static String COLUMN_TOTAL_SIZE_BYTES = "total_size"; 92 93 /** 94 * Uri where downloaded file will be stored. If a destination is supplied by client, that URI 95 * will be used here. Otherwise, the value will initially be null and will be filled in with a 96 * generated URI once the download has started. 97 */ 98 public final static String COLUMN_LOCAL_URI = "local_uri"; 99 100 /** 101 * The pathname of the file where the download is stored. 102 */ 103 public final static String COLUMN_LOCAL_FILENAME = "local_filename"; 104 105 /** 106 * Current status of the download, as one of the STATUS_* constants. 107 */ 108 public final static String COLUMN_STATUS = Downloads.Impl.COLUMN_STATUS; 109 110 /** 111 * Provides more detail on the status of the download. Its meaning depends on the value of 112 * {@link #COLUMN_STATUS}. 113 * 114 * When {@link #COLUMN_STATUS} is {@link #STATUS_FAILED}, this indicates the type of error that 115 * occurred. If an HTTP error occurred, this will hold the HTTP status code as defined in RFC 116 * 2616. Otherwise, it will hold one of the ERROR_* constants. 117 * 118 * When {@link #COLUMN_STATUS} is {@link #STATUS_PAUSED}, this indicates why the download is 119 * paused. It will hold one of the PAUSED_* constants. 120 * 121 * If {@link #COLUMN_STATUS} is neither {@link #STATUS_FAILED} nor {@link #STATUS_PAUSED}, this 122 * column's value is undefined. 123 * 124 * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1">RFC 2616 125 * status codes</a> 126 */ 127 public final static String COLUMN_REASON = "reason"; 128 129 /** 130 * Number of bytes download so far. 131 */ 132 public final static String COLUMN_BYTES_DOWNLOADED_SO_FAR = "bytes_so_far"; 133 134 /** 135 * Timestamp when the download was last modified, in {@link System#currentTimeMillis 136 * System.currentTimeMillis()} (wall clock time in UTC). 137 */ 138 public final static String COLUMN_LAST_MODIFIED_TIMESTAMP = "last_modified_timestamp"; 139 140 /** 141 * The URI to the corresponding entry in MediaProvider for this downloaded entry. It is 142 * used to delete the entries from MediaProvider database when it is deleted from the 143 * downloaded list. 144 */ 145 public static final String COLUMN_MEDIAPROVIDER_URI = Downloads.Impl.COLUMN_MEDIAPROVIDER_URI; 146 147 /** 148 * Value of {@link #COLUMN_STATUS} when the download is waiting to start. 149 */ 150 public final static int STATUS_PENDING = 1 << 0; 151 152 /** 153 * Value of {@link #COLUMN_STATUS} when the download is currently running. 154 */ 155 public final static int STATUS_RUNNING = 1 << 1; 156 157 /** 158 * Value of {@link #COLUMN_STATUS} when the download is waiting to retry or resume. 159 */ 160 public final static int STATUS_PAUSED = 1 << 2; 161 162 /** 163 * Value of {@link #COLUMN_STATUS} when the download has successfully completed. 164 */ 165 public final static int STATUS_SUCCESSFUL = 1 << 3; 166 167 /** 168 * Value of {@link #COLUMN_STATUS} when the download has failed (and will not be retried). 169 */ 170 public final static int STATUS_FAILED = 1 << 4; 171 172 173 /** 174 * Value of COLUMN_ERROR_CODE when the download has completed with an error that doesn't fit 175 * under any other error code. 176 */ 177 public final static int ERROR_UNKNOWN = 1000; 178 179 /** 180 * Value of {@link #COLUMN_REASON} when a storage issue arises which doesn't fit under any 181 * other error code. Use the more specific {@link #ERROR_INSUFFICIENT_SPACE} and 182 * {@link #ERROR_DEVICE_NOT_FOUND} when appropriate. 183 */ 184 public final static int ERROR_FILE_ERROR = 1001; 185 186 /** 187 * Value of {@link #COLUMN_REASON} when an HTTP code was received that download manager 188 * can't handle. 189 */ 190 public final static int ERROR_UNHANDLED_HTTP_CODE = 1002; 191 192 /** 193 * Value of {@link #COLUMN_REASON} when an error receiving or processing data occurred at 194 * the HTTP level. 195 */ 196 public final static int ERROR_HTTP_DATA_ERROR = 1004; 197 198 /** 199 * Value of {@link #COLUMN_REASON} when there were too many redirects. 200 */ 201 public final static int ERROR_TOO_MANY_REDIRECTS = 1005; 202 203 /** 204 * Value of {@link #COLUMN_REASON} when there was insufficient storage space. Typically, 205 * this is because the SD card is full. 206 */ 207 public final static int ERROR_INSUFFICIENT_SPACE = 1006; 208 209 /** 210 * Value of {@link #COLUMN_REASON} when no external storage device was found. Typically, 211 * this is because the SD card is not mounted. 212 */ 213 public final static int ERROR_DEVICE_NOT_FOUND = 1007; 214 215 /** 216 * Value of {@link #COLUMN_REASON} when some possibly transient error occurred but we can't 217 * resume the download. 218 */ 219 public final static int ERROR_CANNOT_RESUME = 1008; 220 221 /** 222 * Value of {@link #COLUMN_REASON} when the requested destination file already exists (the 223 * download manager will not overwrite an existing file). 224 */ 225 public final static int ERROR_FILE_ALREADY_EXISTS = 1009; 226 227 /** 228 * Value of {@link #COLUMN_REASON} when the download is paused because some network error 229 * occurred and the download manager is waiting before retrying the request. 230 */ 231 public final static int PAUSED_WAITING_TO_RETRY = 1; 232 233 /** 234 * Value of {@link #COLUMN_REASON} when the download is waiting for network connectivity to 235 * proceed. 236 */ 237 public final static int PAUSED_WAITING_FOR_NETWORK = 2; 238 239 /** 240 * Value of {@link #COLUMN_REASON} when the download exceeds a size limit for downloads over 241 * the mobile network and the download manager is waiting for a Wi-Fi connection to proceed. 242 */ 243 public final static int PAUSED_QUEUED_FOR_WIFI = 3; 244 245 /** 246 * Value of {@link #COLUMN_REASON} when the download is paused for some other reason. 247 */ 248 public final static int PAUSED_UNKNOWN = 4; 249 250 /** 251 * Broadcast intent action sent by the download manager when a download completes. 252 */ 253 public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE"; 254 255 /** 256 * Broadcast intent action sent by the download manager when the user clicks on a running 257 * download, either from a system notification or from the downloads UI. 258 */ 259 public final static String ACTION_NOTIFICATION_CLICKED = 260 "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED"; 261 262 /** 263 * Intent action to launch an activity to display all downloads. 264 */ 265 public final static String ACTION_VIEW_DOWNLOADS = "android.intent.action.VIEW_DOWNLOADS"; 266 267 /** 268 * Intent extra included with {@link #ACTION_DOWNLOAD_COMPLETE} intents, indicating the ID (as a 269 * long) of the download that just completed. 270 */ 271 public static final String EXTRA_DOWNLOAD_ID = "extra_download_id"; 272 273 /** 274 * When clicks on multiple notifications are received, the following 275 * provides an array of download ids corresponding to the download notification that was 276 * clicked. It can be retrieved by the receiver of this 277 * Intent using {@link android.content.Intent#getLongArrayExtra(String)}. 278 */ 279 public static final String EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS = "extra_click_download_ids"; 280 281 /** 282 * columns to request from DownloadProvider. 283 * @hide 284 */ 285 public static final String[] UNDERLYING_COLUMNS = new String[] { 286 Downloads.Impl._ID, 287 Downloads.Impl._DATA + " AS " + COLUMN_LOCAL_FILENAME, 288 Downloads.Impl.COLUMN_MEDIAPROVIDER_URI, 289 Downloads.Impl.COLUMN_DESTINATION, 290 Downloads.Impl.COLUMN_TITLE, 291 Downloads.Impl.COLUMN_DESCRIPTION, 292 Downloads.Impl.COLUMN_URI, 293 Downloads.Impl.COLUMN_STATUS, 294 Downloads.Impl.COLUMN_FILE_NAME_HINT, 295 Downloads.Impl.COLUMN_MIME_TYPE + " AS " + COLUMN_MEDIA_TYPE, 296 Downloads.Impl.COLUMN_TOTAL_BYTES + " AS " + COLUMN_TOTAL_SIZE_BYTES, 297 Downloads.Impl.COLUMN_LAST_MODIFICATION + " AS " + COLUMN_LAST_MODIFIED_TIMESTAMP, 298 Downloads.Impl.COLUMN_CURRENT_BYTES + " AS " + COLUMN_BYTES_DOWNLOADED_SO_FAR, 299 /* add the following 'computed' columns to the cursor. 300 * they are not 'returned' by the database, but their inclusion 301 * eliminates need to have lot of methods in CursorTranslator 302 */ 303 "'placeholder' AS " + COLUMN_LOCAL_URI, 304 "'placeholder' AS " + COLUMN_REASON 305 }; 306 307 /** 308 * This class contains all the information necessary to request a new download. The URI is the 309 * only required parameter. 310 * 311 * Note that the default download destination is a shared volume where the system might delete 312 * your file if it needs to reclaim space for system use. If this is a problem, use a location 313 * on external storage (see {@link #setDestinationUri(Uri)}. 314 */ 315 public static class Request { 316 /** 317 * Bit flag for {@link #setAllowedNetworkTypes} corresponding to 318 * {@link ConnectivityManager#TYPE_MOBILE}. 319 */ 320 public static final int NETWORK_MOBILE = 1 << 0; 321 322 /** 323 * Bit flag for {@link #setAllowedNetworkTypes} corresponding to 324 * {@link ConnectivityManager#TYPE_WIFI}. 325 */ 326 public static final int NETWORK_WIFI = 1 << 1; 327 328 private Uri mUri; 329 private Uri mDestinationUri; 330 private List<Pair<String, String>> mRequestHeaders = new ArrayList<Pair<String, String>>(); 331 private CharSequence mTitle; 332 private CharSequence mDescription; 333 private String mMimeType; 334 private boolean mRoamingAllowed = true; 335 private int mAllowedNetworkTypes = ~0; // default to all network types allowed 336 private boolean mIsVisibleInDownloadsUi = true; 337 private boolean mScannable = false; 338 /** if a file is designated as a MediaScanner scannable file, the following value is 339 * stored in the database column {@link Downloads.Impl#COLUMN_MEDIA_SCANNED}. 340 */ 341 private static final int SCANNABLE_VALUE_YES = 0; 342 // value of 1 is stored in the above column by DownloadProvider after it is scanned by 343 // MediaScanner 344 /** if a file is designated as a file that should not be scanned by MediaScanner, 345 * the following value is stored in the database column 346 * {@link Downloads.Impl#COLUMN_MEDIA_SCANNED}. 347 */ 348 private static final int SCANNABLE_VALUE_NO = 2; 349 350 /** 351 * This download is visible but only shows in the notifications 352 * while it's in progress. 353 */ 354 public static final int VISIBILITY_VISIBLE = 0; 355 356 /** 357 * This download is visible and shows in the notifications while 358 * in progress and after completion. 359 */ 360 public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1; 361 362 /** 363 * This download doesn't show in the UI or in the notifications. 364 */ 365 public static final int VISIBILITY_HIDDEN = 2; 366 367 /** can take any of the following values: {@link #VISIBILITY_HIDDEN} 368 * {@link #VISIBILITY_VISIBLE_NOTIFY_COMPLETED}, {@link #VISIBILITY_VISIBLE} 369 */ 370 private int mNotificationVisibility = VISIBILITY_VISIBLE; 371 372 /** 373 * @param uri the HTTP URI to download. 374 */ 375 public Request(Uri uri) { 376 if (uri == null) { 377 throw new NullPointerException(); 378 } 379 String scheme = uri.getScheme(); 380 if (scheme == null || (!scheme.equals("http") && !scheme.equals("https"))) { 381 throw new IllegalArgumentException("Can only download HTTP/HTTPS URIs: " + uri); 382 } 383 mUri = uri; 384 } 385 386 /** 387 * Set the local destination for the downloaded file. Must be a file URI to a path on 388 * external storage, and the calling application must have the WRITE_EXTERNAL_STORAGE 389 * permission. 390 * <p> 391 * The downloaded file is not scanned by MediaScanner. 392 * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}. 393 * <p> 394 * By default, downloads are saved to a generated filename in the shared download cache and 395 * may be deleted by the system at any time to reclaim space. 396 * 397 * @return this object 398 */ 399 public Request setDestinationUri(Uri uri) { 400 mDestinationUri = uri; 401 return this; 402 } 403 404 /** 405 * Set the local destination for the downloaded file to a path within the application's 406 * external files directory (as returned by {@link Context#getExternalFilesDir(String)}. 407 * <p> 408 * The downloaded file is not scanned by MediaScanner. 409 * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}. 410 * 411 * @param context the {@link Context} to use in determining the external files directory 412 * @param dirType the directory type to pass to {@link Context#getExternalFilesDir(String)} 413 * @param subPath the path within the external directory, including the destination filename 414 * @return this object 415 */ 416 public Request setDestinationInExternalFilesDir(Context context, String dirType, 417 String subPath) { 418 setDestinationFromBase(context.getExternalFilesDir(dirType), subPath); 419 return this; 420 } 421 422 /** 423 * Set the local destination for the downloaded file to a path within the public external 424 * storage directory (as returned by 425 * {@link Environment#getExternalStoragePublicDirectory(String)}. 426 *<p> 427 * The downloaded file is not scanned by MediaScanner. 428 * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}. 429 * 430 * @param dirType the directory type to pass to 431 * {@link Environment#getExternalStoragePublicDirectory(String)} 432 * @param subPath the path within the external directory, including the destination filename 433 * @return this object 434 */ 435 public Request setDestinationInExternalPublicDir(String dirType, String subPath) { 436 setDestinationFromBase(Environment.getExternalStoragePublicDirectory(dirType), subPath); 437 return this; 438 } 439 440 private void setDestinationFromBase(File base, String subPath) { 441 if (subPath == null) { 442 throw new NullPointerException("subPath cannot be null"); 443 } 444 mDestinationUri = Uri.withAppendedPath(Uri.fromFile(base), subPath); 445 } 446 447 /** 448 * If the file to be downloaded is to be scanned by MediaScanner, this method 449 * should be called before {@link DownloadManager#enqueue(Request)} is called. 450 */ 451 public void allowScanningByMediaScanner() { 452 mScannable = true; 453 } 454 455 /** 456 * Add an HTTP header to be included with the download request. The header will be added to 457 * the end of the list. 458 * @param header HTTP header name 459 * @param value header value 460 * @return this object 461 * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">HTTP/1.1 462 * Message Headers</a> 463 */ 464 public Request addRequestHeader(String header, String value) { 465 if (header == null) { 466 throw new NullPointerException("header cannot be null"); 467 } 468 if (header.contains(":")) { 469 throw new IllegalArgumentException("header may not contain ':'"); 470 } 471 if (value == null) { 472 value = ""; 473 } 474 mRequestHeaders.add(Pair.create(header, value)); 475 return this; 476 } 477 478 /** 479 * Set the title of this download, to be displayed in notifications (if enabled). If no 480 * title is given, a default one will be assigned based on the download filename, once the 481 * download starts. 482 * @return this object 483 */ 484 public Request setTitle(CharSequence title) { 485 mTitle = title; 486 return this; 487 } 488 489 /** 490 * Set a description of this download, to be displayed in notifications (if enabled) 491 * @return this object 492 */ 493 public Request setDescription(CharSequence description) { 494 mDescription = description; 495 return this; 496 } 497 498 /** 499 * Set the MIME content type of this download. This will override the content type declared 500 * in the server's response. 501 * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7">HTTP/1.1 502 * Media Types</a> 503 * @return this object 504 */ 505 public Request setMimeType(String mimeType) { 506 mMimeType = mimeType; 507 return this; 508 } 509 510 /** 511 * Control whether a system notification is posted by the download manager while this 512 * download is running. If enabled, the download manager posts notifications about downloads 513 * through the system {@link android.app.NotificationManager}. By default, a notification is 514 * shown. 515 * 516 * If set to false, this requires the permission 517 * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION. 518 * 519 * @param show whether the download manager should show a notification for this download. 520 * @return this object 521 * @deprecated use {@link #setNotificationVisibility(int)} 522 */ 523 @Deprecated 524 public Request setShowRunningNotification(boolean show) { 525 return (show) ? setNotificationVisibility(VISIBILITY_VISIBLE) : 526 setNotificationVisibility(VISIBILITY_HIDDEN); 527 } 528 529 /** 530 * Control whether a system notification is posted by the download manager while this 531 * download is running or when it is completed. 532 * If enabled, the download manager posts notifications about downloads 533 * through the system {@link android.app.NotificationManager}. 534 * By default, a notification is shown only when the download is in progress. 535 *<p> 536 * It can take the following values: {@link #VISIBILITY_HIDDEN}, 537 * {@link #VISIBILITY_VISIBLE}, 538 * {@link #VISIBILITY_VISIBLE_NOTIFY_COMPLETED}. 539 *<p> 540 * If set to {@link #VISIBILITY_HIDDEN}, this requires the permission 541 * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION. 542 * 543 * @param visibility the visibility setting value 544 * @return this object 545 */ 546 public Request setNotificationVisibility(int visibility) { 547 mNotificationVisibility = visibility; 548 return this; 549 } 550 551 /** 552 * Restrict the types of networks over which this download may proceed. By default, all 553 * network types are allowed. 554 * @param flags any combination of the NETWORK_* bit flags. 555 * @return this object 556 */ 557 public Request setAllowedNetworkTypes(int flags) { 558 mAllowedNetworkTypes = flags; 559 return this; 560 } 561 562 /** 563 * Set whether this download may proceed over a roaming connection. By default, roaming is 564 * allowed. 565 * @param allowed whether to allow a roaming connection to be used 566 * @return this object 567 */ 568 public Request setAllowedOverRoaming(boolean allowed) { 569 mRoamingAllowed = allowed; 570 return this; 571 } 572 573 /** 574 * Set whether this download should be displayed in the system's Downloads UI. True by 575 * default. 576 * @param isVisible whether to display this download in the Downloads UI 577 * @return this object 578 */ 579 public Request setVisibleInDownloadsUi(boolean isVisible) { 580 mIsVisibleInDownloadsUi = isVisible; 581 return this; 582 } 583 584 /** 585 * @return ContentValues to be passed to DownloadProvider.insert() 586 */ 587 ContentValues toContentValues(String packageName) { 588 ContentValues values = new ContentValues(); 589 assert mUri != null; 590 values.put(Downloads.Impl.COLUMN_URI, mUri.toString()); 591 values.put(Downloads.Impl.COLUMN_IS_PUBLIC_API, true); 592 values.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, packageName); 593 594 if (mDestinationUri != null) { 595 values.put(Downloads.Impl.COLUMN_DESTINATION, Downloads.Impl.DESTINATION_FILE_URI); 596 values.put(Downloads.Impl.COLUMN_FILE_NAME_HINT, mDestinationUri.toString()); 597 } else { 598 values.put(Downloads.Impl.COLUMN_DESTINATION, 599 Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE); 600 } 601 // is the file supposed to be media-scannable? 602 values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED, (mScannable) ? SCANNABLE_VALUE_YES : 603 SCANNABLE_VALUE_NO); 604 605 if (!mRequestHeaders.isEmpty()) { 606 encodeHttpHeaders(values); 607 } 608 609 putIfNonNull(values, Downloads.Impl.COLUMN_TITLE, mTitle); 610 putIfNonNull(values, Downloads.Impl.COLUMN_DESCRIPTION, mDescription); 611 putIfNonNull(values, Downloads.Impl.COLUMN_MIME_TYPE, mMimeType); 612 613 values.put(Downloads.Impl.COLUMN_VISIBILITY, mNotificationVisibility); 614 values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes); 615 values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed); 616 values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, mIsVisibleInDownloadsUi); 617 618 return values; 619 } 620 621 private void encodeHttpHeaders(ContentValues values) { 622 int index = 0; 623 for (Pair<String, String> header : mRequestHeaders) { 624 String headerString = header.first + ": " + header.second; 625 values.put(Downloads.Impl.RequestHeaders.INSERT_KEY_PREFIX + index, headerString); 626 index++; 627 } 628 } 629 630 private void putIfNonNull(ContentValues contentValues, String key, Object value) { 631 if (value != null) { 632 contentValues.put(key, value.toString()); 633 } 634 } 635 } 636 637 /** 638 * This class may be used to filter download manager queries. 639 */ 640 public static class Query { 641 /** 642 * Constant for use with {@link #orderBy} 643 * @hide 644 */ 645 public static final int ORDER_ASCENDING = 1; 646 647 /** 648 * Constant for use with {@link #orderBy} 649 * @hide 650 */ 651 public static final int ORDER_DESCENDING = 2; 652 653 private long[] mIds = null; 654 private Integer mStatusFlags = null; 655 private String mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION; 656 private int mOrderDirection = ORDER_DESCENDING; 657 private boolean mOnlyIncludeVisibleInDownloadsUi = false; 658 659 /** 660 * Include only the downloads with the given IDs. 661 * @return this object 662 */ 663 public Query setFilterById(long... ids) { 664 mIds = ids; 665 return this; 666 } 667 668 /** 669 * Include only downloads with status matching any the given status flags. 670 * @param flags any combination of the STATUS_* bit flags 671 * @return this object 672 */ 673 public Query setFilterByStatus(int flags) { 674 mStatusFlags = flags; 675 return this; 676 } 677 678 /** 679 * Controls whether this query includes downloads not visible in the system's Downloads UI. 680 * @param value if true, this query will only include downloads that should be displayed in 681 * the system's Downloads UI; if false (the default), this query will include 682 * both visible and invisible downloads. 683 * @return this object 684 * @hide 685 */ 686 public Query setOnlyIncludeVisibleInDownloadsUi(boolean value) { 687 mOnlyIncludeVisibleInDownloadsUi = value; 688 return this; 689 } 690 691 /** 692 * Change the sort order of the returned Cursor. 693 * 694 * @param column one of the COLUMN_* constants; currently, only 695 * {@link #COLUMN_LAST_MODIFIED_TIMESTAMP} and {@link #COLUMN_TOTAL_SIZE_BYTES} are 696 * supported. 697 * @param direction either {@link #ORDER_ASCENDING} or {@link #ORDER_DESCENDING} 698 * @return this object 699 * @hide 700 */ 701 public Query orderBy(String column, int direction) { 702 if (direction != ORDER_ASCENDING && direction != ORDER_DESCENDING) { 703 throw new IllegalArgumentException("Invalid direction: " + direction); 704 } 705 706 if (column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP)) { 707 mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION; 708 } else if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) { 709 mOrderByColumn = Downloads.Impl.COLUMN_TOTAL_BYTES; 710 } else { 711 throw new IllegalArgumentException("Cannot order by " + column); 712 } 713 mOrderDirection = direction; 714 return this; 715 } 716 717 /** 718 * Run this query using the given ContentResolver. 719 * @param projection the projection to pass to ContentResolver.query() 720 * @return the Cursor returned by ContentResolver.query() 721 */ 722 Cursor runQuery(ContentResolver resolver, String[] projection, Uri baseUri) { 723 Uri uri = baseUri; 724 List<String> selectionParts = new ArrayList<String>(); 725 String[] selectionArgs = null; 726 727 if (mIds != null) { 728 selectionParts.add(getWhereClauseForIds(mIds)); 729 selectionArgs = getWhereArgsForIds(mIds); 730 } 731 732 if (mStatusFlags != null) { 733 List<String> parts = new ArrayList<String>(); 734 if ((mStatusFlags & STATUS_PENDING) != 0) { 735 parts.add(statusClause("=", Downloads.Impl.STATUS_PENDING)); 736 } 737 if ((mStatusFlags & STATUS_RUNNING) != 0) { 738 parts.add(statusClause("=", Downloads.Impl.STATUS_RUNNING)); 739 } 740 if ((mStatusFlags & STATUS_PAUSED) != 0) { 741 parts.add(statusClause("=", Downloads.Impl.STATUS_PAUSED_BY_APP)); 742 parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_TO_RETRY)); 743 parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_FOR_NETWORK)); 744 parts.add(statusClause("=", Downloads.Impl.STATUS_QUEUED_FOR_WIFI)); 745 } 746 if ((mStatusFlags & STATUS_SUCCESSFUL) != 0) { 747 parts.add(statusClause("=", Downloads.Impl.STATUS_SUCCESS)); 748 } 749 if ((mStatusFlags & STATUS_FAILED) != 0) { 750 parts.add("(" + statusClause(">=", 400) 751 + " AND " + statusClause("<", 600) + ")"); 752 } 753 selectionParts.add(joinStrings(" OR ", parts)); 754 } 755 756 if (mOnlyIncludeVisibleInDownloadsUi) { 757 selectionParts.add(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI + " != '0'"); 758 } 759 760 // only return rows which are not marked 'deleted = 1' 761 selectionParts.add(Downloads.Impl.COLUMN_DELETED + " != '1'"); 762 763 String selection = joinStrings(" AND ", selectionParts); 764 String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC"); 765 String orderBy = mOrderByColumn + " " + orderDirection; 766 767 return resolver.query(uri, projection, selection, selectionArgs, orderBy); 768 } 769 770 private String joinStrings(String joiner, Iterable<String> parts) { 771 StringBuilder builder = new StringBuilder(); 772 boolean first = true; 773 for (String part : parts) { 774 if (!first) { 775 builder.append(joiner); 776 } 777 builder.append(part); 778 first = false; 779 } 780 return builder.toString(); 781 } 782 783 private String statusClause(String operator, int value) { 784 return Downloads.Impl.COLUMN_STATUS + operator + "'" + value + "'"; 785 } 786 } 787 788 private ContentResolver mResolver; 789 private String mPackageName; 790 private Uri mBaseUri = Downloads.Impl.CONTENT_URI; 791 792 /** 793 * @hide 794 */ 795 public DownloadManager(ContentResolver resolver, String packageName) { 796 mResolver = resolver; 797 mPackageName = packageName; 798 } 799 800 /** 801 * Makes this object access the download provider through /all_downloads URIs rather than 802 * /my_downloads URIs, for clients that have permission to do so. 803 * @hide 804 */ 805 public void setAccessAllDownloads(boolean accessAllDownloads) { 806 if (accessAllDownloads) { 807 mBaseUri = Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI; 808 } else { 809 mBaseUri = Downloads.Impl.CONTENT_URI; 810 } 811 } 812 813 /** 814 * Enqueue a new download. The download will start automatically once the download manager is 815 * ready to execute it and connectivity is available. 816 * 817 * @param request the parameters specifying this download 818 * @return an ID for the download, unique across the system. This ID is used to make future 819 * calls related to this download. 820 */ 821 public long enqueue(Request request) { 822 ContentValues values = request.toContentValues(mPackageName); 823 Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values); 824 long id = Long.parseLong(downloadUri.getLastPathSegment()); 825 return id; 826 } 827 828 /** 829 * Marks the specified download as 'to be deleted'. This is done when a completed download 830 * is to be removed but the row was stored without enough info to delete the corresponding 831 * metadata from Mediaprovider database. Actual cleanup of this row is done in DownloadService. 832 * 833 * @param ids the IDs of the downloads to be marked 'deleted' 834 * @return the number of downloads actually updated 835 * @hide 836 */ 837 public int markRowDeleted(long... ids) { 838 if (ids == null || ids.length == 0) { 839 // called with nothing to remove! 840 throw new IllegalArgumentException("input param 'ids' can't be null"); 841 } 842 ContentValues values = new ContentValues(); 843 values.put(Downloads.Impl.COLUMN_DELETED, 1); 844 return mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), 845 getWhereArgsForIds(ids)); 846 } 847 848 /** 849 * Cancel downloads and remove them from the download manager. Each download will be stopped if 850 * it was running, and it will no longer be accessible through the download manager. If a file 851 * was already downloaded to external storage, it will not be deleted. 852 * 853 * @param ids the IDs of the downloads to remove 854 * @return the number of downloads actually removed 855 */ 856 public int remove(long... ids) { 857 return markRowDeleted(ids); 858 } 859 860 /** 861 * Query the download manager about downloads that have been requested. 862 * @param query parameters specifying filters for this query 863 * @return a Cursor over the result set of downloads, with columns consisting of all the 864 * COLUMN_* constants. 865 */ 866 public Cursor query(Query query) { 867 Cursor underlyingCursor = query.runQuery(mResolver, UNDERLYING_COLUMNS, mBaseUri); 868 if (underlyingCursor == null) { 869 return null; 870 } 871 return new CursorTranslator(underlyingCursor, mBaseUri); 872 } 873 874 /** 875 * Open a downloaded file for reading. The download must have completed. 876 * @param id the ID of the download 877 * @return a read-only {@link ParcelFileDescriptor} 878 * @throws FileNotFoundException if the destination file does not already exist 879 */ 880 public ParcelFileDescriptor openDownloadedFile(long id) throws FileNotFoundException { 881 return mResolver.openFileDescriptor(getDownloadUri(id), "r"); 882 } 883 884 /** 885 * Returns {@link Uri} for the given downloaded file id, if the file is 886 * downloaded successfully. otherwise, null is returned. 887 *<p> 888 * If the specified downloaded file is in external storage (for example, /sdcard dir), 889 * then it is assumed to be safe for anyone to read and the returned {@link Uri} can be used 890 * by any app to access the downloaded file. 891 * 892 * @param id the id of the downloaded file. 893 * @return the {@link Uri} for the given downloaded file id, if download was successful. null 894 * otherwise. 895 */ 896 public Uri getUriForDownloadedFile(long id) { 897 // to check if the file is in cache, get its destination from the database 898 Query query = new Query().setFilterById(id); 899 Cursor cursor = null; 900 try { 901 cursor = query(query); 902 if (cursor == null) { 903 return null; 904 } 905 while (cursor.moveToFirst()) { 906 int status = cursor.getInt(cursor.getColumnIndexOrThrow( 907 DownloadManager.COLUMN_STATUS)); 908 if (DownloadManager.STATUS_SUCCESSFUL == status) { 909 int indx = cursor.getColumnIndexOrThrow( 910 Downloads.Impl.COLUMN_DESTINATION); 911 int destination = cursor.getInt(indx); 912 // TODO: if we ever add API to DownloadManager to let the caller specify 913 // non-external storage for a downloaded file, then the following code 914 // should also check for that destination. 915 if (destination == Downloads.Impl.DESTINATION_CACHE_PARTITION || 916 destination == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING || 917 destination == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE) { 918 // return private uri 919 return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, id); 920 } else { 921 // return public uri 922 return ContentUris.withAppendedId( 923 Downloads.Impl.PUBLICLY_ACCESSIBLE_DOWNLOADS_URI, id); 924 } 925 } 926 } 927 } finally { 928 if (cursor != null) { 929 cursor.close(); 930 } 931 } 932 // downloaded file not found or its status is not 'successfully completed' 933 return null; 934 } 935 936 /** 937 * Restart the given downloads, which must have already completed (successfully or not). This 938 * method will only work when called from within the download manager's process. 939 * @param ids the IDs of the downloads 940 * @hide 941 */ 942 public void restartDownload(long... ids) { 943 Cursor cursor = query(new Query().setFilterById(ids)); 944 try { 945 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 946 int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS)); 947 if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) { 948 throw new IllegalArgumentException("Cannot restart incomplete download: " 949 + cursor.getLong(cursor.getColumnIndex(COLUMN_ID))); 950 } 951 } 952 } finally { 953 cursor.close(); 954 } 955 956 ContentValues values = new ContentValues(); 957 values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0); 958 values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1); 959 values.putNull(Downloads.Impl._DATA); 960 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING); 961 mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids)); 962 } 963 964 /** 965 * Get the DownloadProvider URI for the download with the given ID. 966 */ 967 Uri getDownloadUri(long id) { 968 return ContentUris.withAppendedId(mBaseUri, id); 969 } 970 971 /** 972 * Get a parameterized SQL WHERE clause to select a bunch of IDs. 973 */ 974 static String getWhereClauseForIds(long[] ids) { 975 StringBuilder whereClause = new StringBuilder(); 976 whereClause.append("("); 977 for (int i = 0; i < ids.length; i++) { 978 if (i > 0) { 979 whereClause.append("OR "); 980 } 981 whereClause.append(Downloads.Impl._ID); 982 whereClause.append(" = ? "); 983 } 984 whereClause.append(")"); 985 return whereClause.toString(); 986 } 987 988 /** 989 * Get the selection args for a clause returned by {@link #getWhereClauseForIds(long[])}. 990 */ 991 static String[] getWhereArgsForIds(long[] ids) { 992 String[] whereArgs = new String[ids.length]; 993 for (int i = 0; i < ids.length; i++) { 994 whereArgs[i] = Long.toString(ids[i]); 995 } 996 return whereArgs; 997 } 998 999 /** 1000 * This class wraps a cursor returned by DownloadProvider -- the "underlying cursor" -- and 1001 * presents a different set of columns, those defined in the DownloadManager.COLUMN_* constants. 1002 * Some columns correspond directly to underlying values while others are computed from 1003 * underlying data. 1004 */ 1005 private static class CursorTranslator extends CursorWrapper { 1006 private Uri mBaseUri; 1007 1008 public CursorTranslator(Cursor cursor, Uri baseUri) { 1009 super(cursor); 1010 mBaseUri = baseUri; 1011 } 1012 1013 @Override 1014 public int getInt(int columnIndex) { 1015 return (int) getLong(columnIndex); 1016 } 1017 1018 @Override 1019 public long getLong(int columnIndex) { 1020 if (getColumnName(columnIndex).equals(COLUMN_REASON)) { 1021 return getReason(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS))); 1022 } else if (getColumnName(columnIndex).equals(COLUMN_STATUS)) { 1023 return translateStatus(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS))); 1024 } else { 1025 return super.getLong(columnIndex); 1026 } 1027 } 1028 1029 @Override 1030 public String getString(int columnIndex) { 1031 return (getColumnName(columnIndex).equals(COLUMN_LOCAL_URI)) ? getLocalUri() : 1032 super.getString(columnIndex); 1033 } 1034 1035 private String getLocalUri() { 1036 long destinationType = getLong(getColumnIndex(Downloads.Impl.COLUMN_DESTINATION)); 1037 if (destinationType == Downloads.Impl.DESTINATION_FILE_URI || 1038 destinationType == Downloads.Impl.DESTINATION_EXTERNAL) { 1039 String localPath = getString(getColumnIndex(COLUMN_LOCAL_FILENAME)); 1040 if (localPath == null) { 1041 return null; 1042 } 1043 return Uri.fromFile(new File(localPath)).toString(); 1044 } 1045 1046 // return content URI for cache download 1047 long downloadId = getLong(getColumnIndex(Downloads.Impl._ID)); 1048 return ContentUris.withAppendedId(mBaseUri, downloadId).toString(); 1049 } 1050 1051 private long getReason(int status) { 1052 switch (translateStatus(status)) { 1053 case STATUS_FAILED: 1054 return getErrorCode(status); 1055 1056 case STATUS_PAUSED: 1057 return getPausedReason(status); 1058 1059 default: 1060 return 0; // arbitrary value when status is not an error 1061 } 1062 } 1063 1064 private long getPausedReason(int status) { 1065 switch (status) { 1066 case Downloads.Impl.STATUS_WAITING_TO_RETRY: 1067 return PAUSED_WAITING_TO_RETRY; 1068 1069 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK: 1070 return PAUSED_WAITING_FOR_NETWORK; 1071 1072 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI: 1073 return PAUSED_QUEUED_FOR_WIFI; 1074 1075 default: 1076 return PAUSED_UNKNOWN; 1077 } 1078 } 1079 1080 private long getErrorCode(int status) { 1081 if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS) 1082 || (500 <= status && status < 600)) { 1083 // HTTP status code 1084 return status; 1085 } 1086 1087 switch (status) { 1088 case Downloads.Impl.STATUS_FILE_ERROR: 1089 return ERROR_FILE_ERROR; 1090 1091 case Downloads.Impl.STATUS_UNHANDLED_HTTP_CODE: 1092 case Downloads.Impl.STATUS_UNHANDLED_REDIRECT: 1093 return ERROR_UNHANDLED_HTTP_CODE; 1094 1095 case Downloads.Impl.STATUS_HTTP_DATA_ERROR: 1096 return ERROR_HTTP_DATA_ERROR; 1097 1098 case Downloads.Impl.STATUS_TOO_MANY_REDIRECTS: 1099 return ERROR_TOO_MANY_REDIRECTS; 1100 1101 case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR: 1102 return ERROR_INSUFFICIENT_SPACE; 1103 1104 case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR: 1105 return ERROR_DEVICE_NOT_FOUND; 1106 1107 case Downloads.Impl.STATUS_CANNOT_RESUME: 1108 return ERROR_CANNOT_RESUME; 1109 1110 case Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR: 1111 return ERROR_FILE_ALREADY_EXISTS; 1112 1113 default: 1114 return ERROR_UNKNOWN; 1115 } 1116 } 1117 1118 private int translateStatus(int status) { 1119 switch (status) { 1120 case Downloads.Impl.STATUS_PENDING: 1121 return STATUS_PENDING; 1122 1123 case Downloads.Impl.STATUS_RUNNING: 1124 return STATUS_RUNNING; 1125 1126 case Downloads.Impl.STATUS_PAUSED_BY_APP: 1127 case Downloads.Impl.STATUS_WAITING_TO_RETRY: 1128 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK: 1129 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI: 1130 return STATUS_PAUSED; 1131 1132 case Downloads.Impl.STATUS_SUCCESS: 1133 return STATUS_SUCCESSFUL; 1134 1135 default: 1136 assert Downloads.Impl.isStatusError(status); 1137 return STATUS_FAILED; 1138 } 1139 } 1140 } 1141} 1142