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