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 17package android.app; 18 19import android.annotation.SdkConstant; 20import android.annotation.SdkConstant.SdkConstantType; 21import android.content.ContentResolver; 22import android.content.ContentUris; 23import android.content.ContentValues; 24import android.content.Context; 25import android.database.Cursor; 26import android.database.CursorWrapper; 27import android.net.ConnectivityManager; 28import android.net.NetworkPolicyManager; 29import android.net.Uri; 30import android.os.Environment; 31import android.os.ParcelFileDescriptor; 32import android.provider.Downloads; 33import android.provider.Settings; 34import android.provider.Settings.SettingNotFoundException; 35import android.text.TextUtils; 36import android.util.Pair; 37 38import java.io.File; 39import java.io.FileNotFoundException; 40import java.util.ArrayList; 41import 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 */ 60public 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 {@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 */ 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 */ 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 */ 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 */ 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 */ 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} */ 1163 public static boolean isActiveNetworkExpensive(Context context) { 1164 // TODO: connect to NetworkPolicyManager 1165 return false; 1166 } 1167 1168 /** {@hide} */ 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 */ 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} */ 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 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 */ 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 */ 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 */ 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 1295 public CursorTranslator(Cursor cursor, Uri baseUri) { 1296 super(cursor); 1297 mBaseUri = baseUri; 1298 } 1299 1300 @Override 1301 public int getInt(int columnIndex) { 1302 return (int) getLong(columnIndex); 1303 } 1304 1305 @Override 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 1317 public String getString(int columnIndex) { 1318 return (getColumnName(columnIndex).equals(COLUMN_LOCAL_URI)) ? getLocalUri() : 1319 super.getString(columnIndex); 1320 } 1321 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 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 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 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 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