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 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 {@link Uri} for the given downloaded file id, if the file is 1015 * downloaded successfully. otherwise, null is returned. 1016 *<p> 1017 * If the specified downloaded file is in external storage (for example, /sdcard dir), 1018 * then it is assumed to be safe for anyone to read and the returned {@link Uri} corresponds 1019 * to the filepath on sdcard. 1020 * 1021 * @param id the id of the downloaded file. 1022 * @return the {@link Uri} for the given downloaded file id, if download was successful. null 1023 * otherwise. 1024 */ getUriForDownloadedFile(long id)1025 public Uri getUriForDownloadedFile(long id) { 1026 // to check if the file is in cache, get its destination from the database 1027 Query query = new Query().setFilterById(id); 1028 Cursor cursor = null; 1029 try { 1030 cursor = query(query); 1031 if (cursor == null) { 1032 return null; 1033 } 1034 if (cursor.moveToFirst()) { 1035 int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS)); 1036 if (DownloadManager.STATUS_SUCCESSFUL == status) { 1037 int indx = cursor.getColumnIndexOrThrow( 1038 Downloads.Impl.COLUMN_DESTINATION); 1039 int destination = cursor.getInt(indx); 1040 // TODO: if we ever add API to DownloadManager to let the caller specify 1041 // non-external storage for a downloaded file, then the following code 1042 // should also check for that destination. 1043 if (destination == Downloads.Impl.DESTINATION_CACHE_PARTITION || 1044 destination == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION || 1045 destination == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING || 1046 destination == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE) { 1047 // return private uri 1048 return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, id); 1049 } else { 1050 // return public uri 1051 String path = cursor.getString( 1052 cursor.getColumnIndexOrThrow(COLUMN_LOCAL_FILENAME)); 1053 return Uri.fromFile(new File(path)); 1054 } 1055 } 1056 } 1057 } finally { 1058 if (cursor != null) { 1059 cursor.close(); 1060 } 1061 } 1062 // downloaded file not found or its status is not 'successfully completed' 1063 return null; 1064 } 1065 1066 /** 1067 * Returns {@link Uri} for the given downloaded file id, if the file is 1068 * downloaded successfully. otherwise, null is returned. 1069 *<p> 1070 * If the specified downloaded file is in external storage (for example, /sdcard dir), 1071 * then it is assumed to be safe for anyone to read and the returned {@link Uri} corresponds 1072 * to the filepath on sdcard. 1073 * 1074 * @param id the id of the downloaded file. 1075 * @return the {@link Uri} for the given downloaded file id, if download was successful. null 1076 * otherwise. 1077 */ getMimeTypeForDownloadedFile(long id)1078 public String getMimeTypeForDownloadedFile(long id) { 1079 Query query = new Query().setFilterById(id); 1080 Cursor cursor = null; 1081 try { 1082 cursor = query(query); 1083 if (cursor == null) { 1084 return null; 1085 } 1086 while (cursor.moveToFirst()) { 1087 return cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_MEDIA_TYPE)); 1088 } 1089 } finally { 1090 if (cursor != null) { 1091 cursor.close(); 1092 } 1093 } 1094 // downloaded file not found or its status is not 'successfully completed' 1095 return null; 1096 } 1097 1098 /** 1099 * Restart the given downloads, which must have already completed (successfully or not). This 1100 * method will only work when called from within the download manager's process. 1101 * @param ids the IDs of the downloads 1102 * @hide 1103 */ restartDownload(long... ids)1104 public void restartDownload(long... ids) { 1105 Cursor cursor = query(new Query().setFilterById(ids)); 1106 try { 1107 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 1108 int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS)); 1109 if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) { 1110 throw new IllegalArgumentException("Cannot restart incomplete download: " 1111 + cursor.getLong(cursor.getColumnIndex(COLUMN_ID))); 1112 } 1113 } 1114 } finally { 1115 cursor.close(); 1116 } 1117 1118 ContentValues values = new ContentValues(); 1119 values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0); 1120 values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1); 1121 values.putNull(Downloads.Impl._DATA); 1122 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING); 1123 values.put(Downloads.Impl.COLUMN_FAILED_CONNECTIONS, 0); 1124 mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids)); 1125 } 1126 1127 /** 1128 * Returns maximum size, in bytes, of downloads that may go over a mobile connection; or null if 1129 * there's no limit 1130 * 1131 * @param context the {@link Context} to use for accessing the {@link ContentResolver} 1132 * @return maximum size, in bytes, of downloads that may go over a mobile connection; or null if 1133 * there's no limit 1134 */ getMaxBytesOverMobile(Context context)1135 public static Long getMaxBytesOverMobile(Context context) { 1136 try { 1137 return Settings.Global.getLong(context.getContentResolver(), 1138 Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE); 1139 } catch (SettingNotFoundException exc) { 1140 return null; 1141 } 1142 } 1143 1144 /** 1145 * Returns recommended maximum size, in bytes, of downloads that may go over a mobile 1146 * connection; or null if there's no recommended limit. The user will have the option to bypass 1147 * this limit. 1148 * 1149 * @param context the {@link Context} to use for accessing the {@link ContentResolver} 1150 * @return recommended maximum size, in bytes, of downloads that may go over a mobile 1151 * connection; or null if there's no recommended limit. 1152 */ getRecommendedMaxBytesOverMobile(Context context)1153 public static Long getRecommendedMaxBytesOverMobile(Context context) { 1154 try { 1155 return Settings.Global.getLong(context.getContentResolver(), 1156 Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE); 1157 } catch (SettingNotFoundException exc) { 1158 return null; 1159 } 1160 } 1161 1162 /** {@hide} */ isActiveNetworkExpensive(Context context)1163 public static boolean isActiveNetworkExpensive(Context context) { 1164 // TODO: connect to NetworkPolicyManager 1165 return false; 1166 } 1167 1168 /** {@hide} */ getActiveNetworkWarningBytes(Context context)1169 public static long getActiveNetworkWarningBytes(Context context) { 1170 // TODO: connect to NetworkPolicyManager 1171 return -1; 1172 } 1173 1174 /** 1175 * Adds a file to the downloads database system, so it could appear in Downloads App 1176 * (and thus become eligible for management by the Downloads App). 1177 * <p> 1178 * It is helpful to make the file scannable by MediaScanner by setting the param 1179 * isMediaScannerScannable to true. It makes the file visible in media managing 1180 * applications such as Gallery App, which could be a useful purpose of using this API. 1181 * 1182 * @param title the title that would appear for this file in Downloads App. 1183 * @param description the description that would appear for this file in Downloads App. 1184 * @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files 1185 * scanned by MediaScanner appear in the applications used to view media (for example, 1186 * Gallery app). 1187 * @param mimeType mimetype of the file. 1188 * @param path absolute pathname to the file. The file should be world-readable, so that it can 1189 * be managed by the Downloads App and any other app that is used to read it (for example, 1190 * Gallery app to display the file, if the file contents represent a video/image). 1191 * @param length length of the downloaded file 1192 * @param showNotification true if a notification is to be sent, false otherwise 1193 * @return an ID for the download entry added to the downloads app, unique across the system 1194 * This ID is used to make future calls related to this download. 1195 */ addCompletedDownload(String title, String description, boolean isMediaScannerScannable, String mimeType, String path, long length, boolean showNotification)1196 public long addCompletedDownload(String title, String description, 1197 boolean isMediaScannerScannable, String mimeType, String path, long length, 1198 boolean showNotification) { 1199 return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path, 1200 length, showNotification, false); 1201 } 1202 1203 /** {@hide} */ addCompletedDownload(String title, String description, boolean isMediaScannerScannable, String mimeType, String path, long length, boolean showNotification, boolean allowWrite)1204 public long addCompletedDownload(String title, String description, 1205 boolean isMediaScannerScannable, String mimeType, String path, long length, 1206 boolean showNotification, boolean allowWrite) { 1207 // make sure the input args are non-null/non-zero 1208 validateArgumentIsNonEmpty("title", title); 1209 validateArgumentIsNonEmpty("description", description); 1210 validateArgumentIsNonEmpty("path", path); 1211 validateArgumentIsNonEmpty("mimeType", mimeType); 1212 if (length < 0) { 1213 throw new IllegalArgumentException(" invalid value for param: totalBytes"); 1214 } 1215 1216 // if there is already an entry with the given path name in downloads.db, return its id 1217 Request request = new Request(NON_DOWNLOADMANAGER_DOWNLOAD) 1218 .setTitle(title) 1219 .setDescription(description) 1220 .setMimeType(mimeType); 1221 ContentValues values = request.toContentValues(null); 1222 values.put(Downloads.Impl.COLUMN_DESTINATION, 1223 Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD); 1224 values.put(Downloads.Impl._DATA, path); 1225 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS); 1226 values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, length); 1227 values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED, 1228 (isMediaScannerScannable) ? Request.SCANNABLE_VALUE_YES : 1229 Request.SCANNABLE_VALUE_NO); 1230 values.put(Downloads.Impl.COLUMN_VISIBILITY, (showNotification) ? 1231 Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION : Request.VISIBILITY_HIDDEN); 1232 values.put(Downloads.Impl.COLUMN_ALLOW_WRITE, allowWrite ? 1 : 0); 1233 Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values); 1234 if (downloadUri == null) { 1235 return -1; 1236 } 1237 return Long.parseLong(downloadUri.getLastPathSegment()); 1238 } 1239 1240 private static final String NON_DOWNLOADMANAGER_DOWNLOAD = 1241 "non-dwnldmngr-download-dont-retry2download"; 1242 validateArgumentIsNonEmpty(String paramName, String val)1243 private static void validateArgumentIsNonEmpty(String paramName, String val) { 1244 if (TextUtils.isEmpty(val)) { 1245 throw new IllegalArgumentException(paramName + " can't be null"); 1246 } 1247 } 1248 1249 /** 1250 * Get the DownloadProvider URI for the download with the given ID. 1251 * 1252 * @hide 1253 */ getDownloadUri(long id)1254 public Uri getDownloadUri(long id) { 1255 return ContentUris.withAppendedId(mBaseUri, id); 1256 } 1257 1258 /** 1259 * Get a parameterized SQL WHERE clause to select a bunch of IDs. 1260 */ getWhereClauseForIds(long[] ids)1261 static String getWhereClauseForIds(long[] ids) { 1262 StringBuilder whereClause = new StringBuilder(); 1263 whereClause.append("("); 1264 for (int i = 0; i < ids.length; i++) { 1265 if (i > 0) { 1266 whereClause.append("OR "); 1267 } 1268 whereClause.append(Downloads.Impl._ID); 1269 whereClause.append(" = ? "); 1270 } 1271 whereClause.append(")"); 1272 return whereClause.toString(); 1273 } 1274 1275 /** 1276 * Get the selection args for a clause returned by {@link #getWhereClauseForIds(long[])}. 1277 */ getWhereArgsForIds(long[] ids)1278 static String[] getWhereArgsForIds(long[] ids) { 1279 String[] whereArgs = new String[ids.length]; 1280 for (int i = 0; i < ids.length; i++) { 1281 whereArgs[i] = Long.toString(ids[i]); 1282 } 1283 return whereArgs; 1284 } 1285 1286 /** 1287 * This class wraps a cursor returned by DownloadProvider -- the "underlying cursor" -- and 1288 * presents a different set of columns, those defined in the DownloadManager.COLUMN_* constants. 1289 * Some columns correspond directly to underlying values while others are computed from 1290 * underlying data. 1291 */ 1292 private static class CursorTranslator extends CursorWrapper { 1293 private Uri mBaseUri; 1294 CursorTranslator(Cursor cursor, Uri baseUri)1295 public CursorTranslator(Cursor cursor, Uri baseUri) { 1296 super(cursor); 1297 mBaseUri = baseUri; 1298 } 1299 1300 @Override getInt(int columnIndex)1301 public int getInt(int columnIndex) { 1302 return (int) getLong(columnIndex); 1303 } 1304 1305 @Override getLong(int columnIndex)1306 public long getLong(int columnIndex) { 1307 if (getColumnName(columnIndex).equals(COLUMN_REASON)) { 1308 return getReason(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS))); 1309 } else if (getColumnName(columnIndex).equals(COLUMN_STATUS)) { 1310 return translateStatus(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS))); 1311 } else { 1312 return super.getLong(columnIndex); 1313 } 1314 } 1315 1316 @Override getString(int columnIndex)1317 public String getString(int columnIndex) { 1318 return (getColumnName(columnIndex).equals(COLUMN_LOCAL_URI)) ? getLocalUri() : 1319 super.getString(columnIndex); 1320 } 1321 getLocalUri()1322 private String getLocalUri() { 1323 long destinationType = getLong(getColumnIndex(Downloads.Impl.COLUMN_DESTINATION)); 1324 if (destinationType == Downloads.Impl.DESTINATION_FILE_URI || 1325 destinationType == Downloads.Impl.DESTINATION_EXTERNAL || 1326 destinationType == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) { 1327 String localPath = getString(getColumnIndex(COLUMN_LOCAL_FILENAME)); 1328 if (localPath == null) { 1329 return null; 1330 } 1331 return Uri.fromFile(new File(localPath)).toString(); 1332 } 1333 1334 // return content URI for cache download 1335 long downloadId = getLong(getColumnIndex(Downloads.Impl._ID)); 1336 return ContentUris.withAppendedId(mBaseUri, downloadId).toString(); 1337 } 1338 getReason(int status)1339 private long getReason(int status) { 1340 switch (translateStatus(status)) { 1341 case STATUS_FAILED: 1342 return getErrorCode(status); 1343 1344 case STATUS_PAUSED: 1345 return getPausedReason(status); 1346 1347 default: 1348 return 0; // arbitrary value when status is not an error 1349 } 1350 } 1351 getPausedReason(int status)1352 private long getPausedReason(int status) { 1353 switch (status) { 1354 case Downloads.Impl.STATUS_WAITING_TO_RETRY: 1355 return PAUSED_WAITING_TO_RETRY; 1356 1357 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK: 1358 return PAUSED_WAITING_FOR_NETWORK; 1359 1360 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI: 1361 return PAUSED_QUEUED_FOR_WIFI; 1362 1363 default: 1364 return PAUSED_UNKNOWN; 1365 } 1366 } 1367 getErrorCode(int status)1368 private long getErrorCode(int status) { 1369 if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS) 1370 || (500 <= status && status < 600)) { 1371 // HTTP status code 1372 return status; 1373 } 1374 1375 switch (status) { 1376 case Downloads.Impl.STATUS_FILE_ERROR: 1377 return ERROR_FILE_ERROR; 1378 1379 case Downloads.Impl.STATUS_UNHANDLED_HTTP_CODE: 1380 case Downloads.Impl.STATUS_UNHANDLED_REDIRECT: 1381 return ERROR_UNHANDLED_HTTP_CODE; 1382 1383 case Downloads.Impl.STATUS_HTTP_DATA_ERROR: 1384 return ERROR_HTTP_DATA_ERROR; 1385 1386 case Downloads.Impl.STATUS_TOO_MANY_REDIRECTS: 1387 return ERROR_TOO_MANY_REDIRECTS; 1388 1389 case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR: 1390 return ERROR_INSUFFICIENT_SPACE; 1391 1392 case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR: 1393 return ERROR_DEVICE_NOT_FOUND; 1394 1395 case Downloads.Impl.STATUS_CANNOT_RESUME: 1396 return ERROR_CANNOT_RESUME; 1397 1398 case Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR: 1399 return ERROR_FILE_ALREADY_EXISTS; 1400 1401 default: 1402 return ERROR_UNKNOWN; 1403 } 1404 } 1405 translateStatus(int status)1406 private int translateStatus(int status) { 1407 switch (status) { 1408 case Downloads.Impl.STATUS_PENDING: 1409 return STATUS_PENDING; 1410 1411 case Downloads.Impl.STATUS_RUNNING: 1412 return STATUS_RUNNING; 1413 1414 case Downloads.Impl.STATUS_PAUSED_BY_APP: 1415 case Downloads.Impl.STATUS_WAITING_TO_RETRY: 1416 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK: 1417 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI: 1418 return STATUS_PAUSED; 1419 1420 case Downloads.Impl.STATUS_SUCCESS: 1421 return STATUS_SUCCESSFUL; 1422 1423 default: 1424 assert Downloads.Impl.isStatusError(status); 1425 return STATUS_FAILED; 1426 } 1427 } 1428 } 1429 } 1430