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