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