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