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