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