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