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