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