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