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