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