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