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 the application's 465 * external files directory (as returned by {@link Context#getExternalFilesDir(String)}. 466 * <p> 467 * The downloaded file is not scanned by MediaScanner. 468 * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}. 469 * 470 * @param context the {@link Context} to use in determining the external files directory 471 * @param dirType the directory type to pass to {@link Context#getExternalFilesDir(String)} 472 * @param subPath the path within the external directory, including the destination filename 473 * @return this object 474 */ 475 public Request setDestinationInExternalFilesDir(Context context, String dirType, 476 String subPath) { 477 setDestinationFromBase(context.getExternalFilesDir(dirType), subPath); 478 return this; 479 } 480 481 /** 482 * Set the local destination for the downloaded file to a path within the public external 483 * storage directory (as returned by 484 * {@link Environment#getExternalStoragePublicDirectory(String)}. 485 *<p> 486 * The downloaded file is not scanned by MediaScanner. 487 * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}. 488 * 489 * @param dirType the directory type to pass to 490 * {@link Environment#getExternalStoragePublicDirectory(String)} 491 * @param subPath the path within the external directory, including the destination filename 492 * @return this object 493 */ 494 public Request setDestinationInExternalPublicDir(String dirType, String subPath) { 495 File file = Environment.getExternalStoragePublicDirectory(dirType); 496 if (file.exists()) { 497 if (!file.isDirectory()) { 498 throw new IllegalStateException(file.getAbsolutePath() + 499 " already exists and is not a directory"); 500 } 501 } else { 502 if (!file.mkdir()) { 503 throw new IllegalStateException("Unable to create directory: "+ 504 file.getAbsolutePath()); 505 } 506 } 507 setDestinationFromBase(file, subPath); 508 return this; 509 } 510 511 private void setDestinationFromBase(File base, String subPath) { 512 if (subPath == null) { 513 throw new NullPointerException("subPath cannot be null"); 514 } 515 mDestinationUri = Uri.withAppendedPath(Uri.fromFile(base), subPath); 516 } 517 518 /** 519 * If the file to be downloaded is to be scanned by MediaScanner, this method 520 * should be called before {@link DownloadManager#enqueue(Request)} is called. 521 */ 522 public void allowScanningByMediaScanner() { 523 mScannable = true; 524 } 525 526 /** 527 * Add an HTTP header to be included with the download request. The header will be added to 528 * the end of the list. 529 * @param header HTTP header name 530 * @param value header value 531 * @return this object 532 * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">HTTP/1.1 533 * Message Headers</a> 534 */ 535 public Request addRequestHeader(String header, String value) { 536 if (header == null) { 537 throw new NullPointerException("header cannot be null"); 538 } 539 if (header.contains(":")) { 540 throw new IllegalArgumentException("header may not contain ':'"); 541 } 542 if (value == null) { 543 value = ""; 544 } 545 mRequestHeaders.add(Pair.create(header, value)); 546 return this; 547 } 548 549 /** 550 * Set the title of this download, to be displayed in notifications (if enabled). If no 551 * title is given, a default one will be assigned based on the download filename, once the 552 * download starts. 553 * @return this object 554 */ 555 public Request setTitle(CharSequence title) { 556 mTitle = title; 557 return this; 558 } 559 560 /** 561 * Set a description of this download, to be displayed in notifications (if enabled) 562 * @return this object 563 */ 564 public Request setDescription(CharSequence description) { 565 mDescription = description; 566 return this; 567 } 568 569 /** 570 * Set the MIME content type of this download. This will override the content type declared 571 * in the server's response. 572 * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7">HTTP/1.1 573 * Media Types</a> 574 * @return this object 575 */ 576 public Request setMimeType(String mimeType) { 577 mMimeType = mimeType; 578 return this; 579 } 580 581 /** 582 * Control whether a system notification is posted by the download manager while this 583 * download is running. If enabled, the download manager posts notifications about downloads 584 * through the system {@link android.app.NotificationManager}. By default, a notification is 585 * shown. 586 * 587 * If set to false, this requires the permission 588 * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION. 589 * 590 * @param show whether the download manager should show a notification for this download. 591 * @return this object 592 * @deprecated use {@link #setNotificationVisibility(int)} 593 */ 594 @Deprecated 595 public Request setShowRunningNotification(boolean show) { 596 return (show) ? setNotificationVisibility(VISIBILITY_VISIBLE) : 597 setNotificationVisibility(VISIBILITY_HIDDEN); 598 } 599 600 /** 601 * Control whether a system notification is posted by the download manager while this 602 * download is running or when it is completed. 603 * If enabled, the download manager posts notifications about downloads 604 * through the system {@link android.app.NotificationManager}. 605 * By default, a notification is shown only when the download is in progress. 606 *<p> 607 * It can take the following values: {@link #VISIBILITY_HIDDEN}, 608 * {@link #VISIBILITY_VISIBLE}, 609 * {@link #VISIBILITY_VISIBLE_NOTIFY_COMPLETED}. 610 *<p> 611 * If set to {@link #VISIBILITY_HIDDEN}, this requires the permission 612 * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION. 613 * 614 * @param visibility the visibility setting value 615 * @return this object 616 */ 617 public Request setNotificationVisibility(int visibility) { 618 mNotificationVisibility = visibility; 619 return this; 620 } 621 622 /** 623 * Restrict the types of networks over which this download may proceed. 624 * By default, all network types are allowed. Consider using 625 * {@link #setAllowedOverMetered(boolean)} instead, since it's more 626 * flexible. 627 * 628 * @param flags any combination of the NETWORK_* bit flags. 629 * @return this object 630 */ 631 public Request setAllowedNetworkTypes(int flags) { 632 mAllowedNetworkTypes = flags; 633 return this; 634 } 635 636 /** 637 * Set whether this download may proceed over a roaming connection. By default, roaming is 638 * allowed. 639 * @param allowed whether to allow a roaming connection to be used 640 * @return this object 641 */ 642 public Request setAllowedOverRoaming(boolean allowed) { 643 mRoamingAllowed = allowed; 644 return this; 645 } 646 647 /** 648 * Set whether this download may proceed over a metered network 649 * connection. By default, metered networks are allowed. 650 * 651 * @see ConnectivityManager#isActiveNetworkMetered() 652 */ 653 public Request setAllowedOverMetered(boolean allow) { 654 mMeteredAllowed = allow; 655 return this; 656 } 657 658 /** 659 * Set whether this download should be displayed in the system's Downloads UI. True by 660 * default. 661 * @param isVisible whether to display this download in the Downloads UI 662 * @return this object 663 */ 664 public Request setVisibleInDownloadsUi(boolean isVisible) { 665 mIsVisibleInDownloadsUi = isVisible; 666 return this; 667 } 668 669 /** 670 * @return ContentValues to be passed to DownloadProvider.insert() 671 */ 672 ContentValues toContentValues(String packageName) { 673 ContentValues values = new ContentValues(); 674 assert mUri != null; 675 values.put(Downloads.Impl.COLUMN_URI, mUri.toString()); 676 values.put(Downloads.Impl.COLUMN_IS_PUBLIC_API, true); 677 values.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, packageName); 678 679 if (mDestinationUri != null) { 680 values.put(Downloads.Impl.COLUMN_DESTINATION, Downloads.Impl.DESTINATION_FILE_URI); 681 values.put(Downloads.Impl.COLUMN_FILE_NAME_HINT, mDestinationUri.toString()); 682 } else { 683 values.put(Downloads.Impl.COLUMN_DESTINATION, 684 (this.mUseSystemCache) ? 685 Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION : 686 Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE); 687 } 688 // is the file supposed to be media-scannable? 689 values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED, (mScannable) ? SCANNABLE_VALUE_YES : 690 SCANNABLE_VALUE_NO); 691 692 if (!mRequestHeaders.isEmpty()) { 693 encodeHttpHeaders(values); 694 } 695 696 putIfNonNull(values, Downloads.Impl.COLUMN_TITLE, mTitle); 697 putIfNonNull(values, Downloads.Impl.COLUMN_DESCRIPTION, mDescription); 698 putIfNonNull(values, Downloads.Impl.COLUMN_MIME_TYPE, mMimeType); 699 700 values.put(Downloads.Impl.COLUMN_VISIBILITY, mNotificationVisibility); 701 values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes); 702 values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed); 703 values.put(Downloads.Impl.COLUMN_ALLOW_METERED, mMeteredAllowed); 704 values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, mIsVisibleInDownloadsUi); 705 706 return values; 707 } 708 709 private void encodeHttpHeaders(ContentValues values) { 710 int index = 0; 711 for (Pair<String, String> header : mRequestHeaders) { 712 String headerString = header.first + ": " + header.second; 713 values.put(Downloads.Impl.RequestHeaders.INSERT_KEY_PREFIX + index, headerString); 714 index++; 715 } 716 } 717 718 private void putIfNonNull(ContentValues contentValues, String key, Object value) { 719 if (value != null) { 720 contentValues.put(key, value.toString()); 721 } 722 } 723 } 724 725 /** 726 * This class may be used to filter download manager queries. 727 */ 728 public static class Query { 729 /** 730 * Constant for use with {@link #orderBy} 731 * @hide 732 */ 733 public static final int ORDER_ASCENDING = 1; 734 735 /** 736 * Constant for use with {@link #orderBy} 737 * @hide 738 */ 739 public static final int ORDER_DESCENDING = 2; 740 741 private long[] mIds = null; 742 private Integer mStatusFlags = null; 743 private String mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION; 744 private int mOrderDirection = ORDER_DESCENDING; 745 private boolean mOnlyIncludeVisibleInDownloadsUi = false; 746 747 /** 748 * Include only the downloads with the given IDs. 749 * @return this object 750 */ 751 public Query setFilterById(long... ids) { 752 mIds = ids; 753 return this; 754 } 755 756 /** 757 * Include only downloads with status matching any the given status flags. 758 * @param flags any combination of the STATUS_* bit flags 759 * @return this object 760 */ 761 public Query setFilterByStatus(int flags) { 762 mStatusFlags = flags; 763 return this; 764 } 765 766 /** 767 * Controls whether this query includes downloads not visible in the system's Downloads UI. 768 * @param value if true, this query will only include downloads that should be displayed in 769 * the system's Downloads UI; if false (the default), this query will include 770 * both visible and invisible downloads. 771 * @return this object 772 * @hide 773 */ 774 public Query setOnlyIncludeVisibleInDownloadsUi(boolean value) { 775 mOnlyIncludeVisibleInDownloadsUi = value; 776 return this; 777 } 778 779 /** 780 * Change the sort order of the returned Cursor. 781 * 782 * @param column one of the COLUMN_* constants; currently, only 783 * {@link #COLUMN_LAST_MODIFIED_TIMESTAMP} and {@link #COLUMN_TOTAL_SIZE_BYTES} are 784 * supported. 785 * @param direction either {@link #ORDER_ASCENDING} or {@link #ORDER_DESCENDING} 786 * @return this object 787 * @hide 788 */ 789 public Query orderBy(String column, int direction) { 790 if (direction != ORDER_ASCENDING && direction != ORDER_DESCENDING) { 791 throw new IllegalArgumentException("Invalid direction: " + direction); 792 } 793 794 if (column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP)) { 795 mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION; 796 } else if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) { 797 mOrderByColumn = Downloads.Impl.COLUMN_TOTAL_BYTES; 798 } else { 799 throw new IllegalArgumentException("Cannot order by " + column); 800 } 801 mOrderDirection = direction; 802 return this; 803 } 804 805 /** 806 * Run this query using the given ContentResolver. 807 * @param projection the projection to pass to ContentResolver.query() 808 * @return the Cursor returned by ContentResolver.query() 809 */ 810 Cursor runQuery(ContentResolver resolver, String[] projection, Uri baseUri) { 811 Uri uri = baseUri; 812 List<String> selectionParts = new ArrayList<String>(); 813 String[] selectionArgs = null; 814 815 if (mIds != null) { 816 selectionParts.add(getWhereClauseForIds(mIds)); 817 selectionArgs = getWhereArgsForIds(mIds); 818 } 819 820 if (mStatusFlags != null) { 821 List<String> parts = new ArrayList<String>(); 822 if ((mStatusFlags & STATUS_PENDING) != 0) { 823 parts.add(statusClause("=", Downloads.Impl.STATUS_PENDING)); 824 } 825 if ((mStatusFlags & STATUS_RUNNING) != 0) { 826 parts.add(statusClause("=", Downloads.Impl.STATUS_RUNNING)); 827 } 828 if ((mStatusFlags & STATUS_PAUSED) != 0) { 829 parts.add(statusClause("=", Downloads.Impl.STATUS_PAUSED_BY_APP)); 830 parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_TO_RETRY)); 831 parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_FOR_NETWORK)); 832 parts.add(statusClause("=", Downloads.Impl.STATUS_QUEUED_FOR_WIFI)); 833 } 834 if ((mStatusFlags & STATUS_SUCCESSFUL) != 0) { 835 parts.add(statusClause("=", Downloads.Impl.STATUS_SUCCESS)); 836 } 837 if ((mStatusFlags & STATUS_FAILED) != 0) { 838 parts.add("(" + statusClause(">=", 400) 839 + " AND " + statusClause("<", 600) + ")"); 840 } 841 selectionParts.add(joinStrings(" OR ", parts)); 842 } 843 844 if (mOnlyIncludeVisibleInDownloadsUi) { 845 selectionParts.add(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI + " != '0'"); 846 } 847 848 // only return rows which are not marked 'deleted = 1' 849 selectionParts.add(Downloads.Impl.COLUMN_DELETED + " != '1'"); 850 851 String selection = joinStrings(" AND ", selectionParts); 852 String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC"); 853 String orderBy = mOrderByColumn + " " + orderDirection; 854 855 return resolver.query(uri, projection, selection, selectionArgs, orderBy); 856 } 857 858 private String joinStrings(String joiner, Iterable<String> parts) { 859 StringBuilder builder = new StringBuilder(); 860 boolean first = true; 861 for (String part : parts) { 862 if (!first) { 863 builder.append(joiner); 864 } 865 builder.append(part); 866 first = false; 867 } 868 return builder.toString(); 869 } 870 871 private String statusClause(String operator, int value) { 872 return Downloads.Impl.COLUMN_STATUS + operator + "'" + value + "'"; 873 } 874 } 875 876 private ContentResolver mResolver; 877 private String mPackageName; 878 private Uri mBaseUri = Downloads.Impl.CONTENT_URI; 879 880 /** 881 * @hide 882 */ 883 public DownloadManager(ContentResolver resolver, String packageName) { 884 mResolver = resolver; 885 mPackageName = packageName; 886 } 887 888 /** 889 * Makes this object access the download provider through /all_downloads URIs rather than 890 * /my_downloads URIs, for clients that have permission to do so. 891 * @hide 892 */ 893 public void setAccessAllDownloads(boolean accessAllDownloads) { 894 if (accessAllDownloads) { 895 mBaseUri = Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI; 896 } else { 897 mBaseUri = Downloads.Impl.CONTENT_URI; 898 } 899 } 900 901 /** 902 * Enqueue a new download. The download will start automatically once the download manager is 903 * ready to execute it and connectivity is available. 904 * 905 * @param request the parameters specifying this download 906 * @return an ID for the download, unique across the system. This ID is used to make future 907 * calls related to this download. 908 */ 909 public long enqueue(Request request) { 910 ContentValues values = request.toContentValues(mPackageName); 911 Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values); 912 long id = Long.parseLong(downloadUri.getLastPathSegment()); 913 return id; 914 } 915 916 /** 917 * Marks the specified download as 'to be deleted'. This is done when a completed download 918 * is to be removed but the row was stored without enough info to delete the corresponding 919 * metadata from Mediaprovider database. Actual cleanup of this row is done in DownloadService. 920 * 921 * @param ids the IDs of the downloads to be marked 'deleted' 922 * @return the number of downloads actually updated 923 * @hide 924 */ 925 public int markRowDeleted(long... ids) { 926 if (ids == null || ids.length == 0) { 927 // called with nothing to remove! 928 throw new IllegalArgumentException("input param 'ids' can't be null"); 929 } 930 ContentValues values = new ContentValues(); 931 values.put(Downloads.Impl.COLUMN_DELETED, 1); 932 // if only one id is passed in, then include it in the uri itself. 933 // this will eliminate a full database scan in the download service. 934 if (ids.length == 1) { 935 return mResolver.update(ContentUris.withAppendedId(mBaseUri, ids[0]), values, 936 null, null); 937 } 938 return mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), 939 getWhereArgsForIds(ids)); 940 } 941 942 /** 943 * Cancel downloads and remove them from the download manager. Each download will be stopped if 944 * it was running, and it will no longer be accessible through the download manager. 945 * If there is a downloaded file, partial or complete, it is deleted. 946 * 947 * @param ids the IDs of the downloads to remove 948 * @return the number of downloads actually removed 949 */ 950 public int remove(long... ids) { 951 return markRowDeleted(ids); 952 } 953 954 /** 955 * Query the download manager about downloads that have been requested. 956 * @param query parameters specifying filters for this query 957 * @return a Cursor over the result set of downloads, with columns consisting of all the 958 * COLUMN_* constants. 959 */ 960 public Cursor query(Query query) { 961 Cursor underlyingCursor = query.runQuery(mResolver, UNDERLYING_COLUMNS, mBaseUri); 962 if (underlyingCursor == null) { 963 return null; 964 } 965 return new CursorTranslator(underlyingCursor, mBaseUri); 966 } 967 968 /** 969 * Open a downloaded file for reading. The download must have completed. 970 * @param id the ID of the download 971 * @return a read-only {@link ParcelFileDescriptor} 972 * @throws FileNotFoundException if the destination file does not already exist 973 */ 974 public ParcelFileDescriptor openDownloadedFile(long id) throws FileNotFoundException { 975 return mResolver.openFileDescriptor(getDownloadUri(id), "r"); 976 } 977 978 /** 979 * Returns {@link Uri} for the given downloaded file id, if the file is 980 * downloaded successfully. otherwise, null is returned. 981 *<p> 982 * If the specified downloaded file is in external storage (for example, /sdcard dir), 983 * then it is assumed to be safe for anyone to read and the returned {@link Uri} corresponds 984 * to the filepath on sdcard. 985 * 986 * @param id the id of the downloaded file. 987 * @return the {@link Uri} for the given downloaded file id, if download was successful. null 988 * otherwise. 989 */ 990 public Uri getUriForDownloadedFile(long id) { 991 // to check if the file is in cache, get its destination from the database 992 Query query = new Query().setFilterById(id); 993 Cursor cursor = null; 994 try { 995 cursor = query(query); 996 if (cursor == null) { 997 return null; 998 } 999 if (cursor.moveToFirst()) { 1000 int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS)); 1001 if (DownloadManager.STATUS_SUCCESSFUL == status) { 1002 int indx = cursor.getColumnIndexOrThrow( 1003 Downloads.Impl.COLUMN_DESTINATION); 1004 int destination = cursor.getInt(indx); 1005 // TODO: if we ever add API to DownloadManager to let the caller specify 1006 // non-external storage for a downloaded file, then the following code 1007 // should also check for that destination. 1008 if (destination == Downloads.Impl.DESTINATION_CACHE_PARTITION || 1009 destination == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION || 1010 destination == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING || 1011 destination == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE) { 1012 // return private uri 1013 return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, id); 1014 } else { 1015 // return public uri 1016 String path = cursor.getString( 1017 cursor.getColumnIndexOrThrow(COLUMN_LOCAL_FILENAME)); 1018 return Uri.fromFile(new File(path)); 1019 } 1020 } 1021 } 1022 } finally { 1023 if (cursor != null) { 1024 cursor.close(); 1025 } 1026 } 1027 // downloaded file not found or its status is not 'successfully completed' 1028 return null; 1029 } 1030 1031 /** 1032 * Returns {@link Uri} for the given downloaded file id, if the file is 1033 * downloaded successfully. otherwise, null is returned. 1034 *<p> 1035 * If the specified downloaded file is in external storage (for example, /sdcard dir), 1036 * then it is assumed to be safe for anyone to read and the returned {@link Uri} corresponds 1037 * to the filepath on sdcard. 1038 * 1039 * @param id the id of the downloaded file. 1040 * @return the {@link Uri} for the given downloaded file id, if download was successful. null 1041 * otherwise. 1042 */ 1043 public String getMimeTypeForDownloadedFile(long id) { 1044 Query query = new Query().setFilterById(id); 1045 Cursor cursor = null; 1046 try { 1047 cursor = query(query); 1048 if (cursor == null) { 1049 return null; 1050 } 1051 while (cursor.moveToFirst()) { 1052 return cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_MEDIA_TYPE)); 1053 } 1054 } finally { 1055 if (cursor != null) { 1056 cursor.close(); 1057 } 1058 } 1059 // downloaded file not found or its status is not 'successfully completed' 1060 return null; 1061 } 1062 1063 /** 1064 * Restart the given downloads, which must have already completed (successfully or not). This 1065 * method will only work when called from within the download manager's process. 1066 * @param ids the IDs of the downloads 1067 * @hide 1068 */ 1069 public void restartDownload(long... ids) { 1070 Cursor cursor = query(new Query().setFilterById(ids)); 1071 try { 1072 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 1073 int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS)); 1074 if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) { 1075 throw new IllegalArgumentException("Cannot restart incomplete download: " 1076 + cursor.getLong(cursor.getColumnIndex(COLUMN_ID))); 1077 } 1078 } 1079 } finally { 1080 cursor.close(); 1081 } 1082 1083 ContentValues values = new ContentValues(); 1084 values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0); 1085 values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1); 1086 values.putNull(Downloads.Impl._DATA); 1087 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING); 1088 mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids)); 1089 } 1090 1091 /** 1092 * Returns maximum size, in bytes, of downloads that may go over a mobile connection; or null if 1093 * there's no limit 1094 * 1095 * @param context the {@link Context} to use for accessing the {@link ContentResolver} 1096 * @return maximum size, in bytes, of downloads that may go over a mobile connection; or null if 1097 * there's no limit 1098 */ 1099 public static Long getMaxBytesOverMobile(Context context) { 1100 try { 1101 return Settings.Global.getLong(context.getContentResolver(), 1102 Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE); 1103 } catch (SettingNotFoundException exc) { 1104 return null; 1105 } 1106 } 1107 1108 /** 1109 * Returns recommended maximum size, in bytes, of downloads that may go over a mobile 1110 * connection; or null if there's no recommended limit. The user will have the option to bypass 1111 * this limit. 1112 * 1113 * @param context the {@link Context} to use for accessing the {@link ContentResolver} 1114 * @return recommended maximum size, in bytes, of downloads that may go over a mobile 1115 * connection; or null if there's no recommended limit. 1116 */ 1117 public static Long getRecommendedMaxBytesOverMobile(Context context) { 1118 try { 1119 return Settings.Global.getLong(context.getContentResolver(), 1120 Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE); 1121 } catch (SettingNotFoundException exc) { 1122 return null; 1123 } 1124 } 1125 1126 /** {@hide} */ 1127 public static boolean isActiveNetworkExpensive(Context context) { 1128 // TODO: connect to NetworkPolicyManager 1129 return false; 1130 } 1131 1132 /** {@hide} */ 1133 public static long getActiveNetworkWarningBytes(Context context) { 1134 // TODO: connect to NetworkPolicyManager 1135 return -1; 1136 } 1137 1138 /** 1139 * Adds a file to the downloads database system, so it could appear in Downloads App 1140 * (and thus become eligible for management by the Downloads App). 1141 * <p> 1142 * It is helpful to make the file scannable by MediaScanner by setting the param 1143 * isMediaScannerScannable to true. It makes the file visible in media managing 1144 * applications such as Gallery App, which could be a useful purpose of using this API. 1145 * 1146 * @param title the title that would appear for this file in Downloads App. 1147 * @param description the description that would appear for this file in Downloads App. 1148 * @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files 1149 * scanned by MediaScanner appear in the applications used to view media (for example, 1150 * Gallery app). 1151 * @param mimeType mimetype of the file. 1152 * @param path absolute pathname to the file. The file should be world-readable, so that it can 1153 * be managed by the Downloads App and any other app that is used to read it (for example, 1154 * Gallery app to display the file, if the file contents represent a video/image). 1155 * @param length length of the downloaded file 1156 * @param showNotification true if a notification is to be sent, false otherwise 1157 * @return an ID for the download entry added to the downloads app, unique across the system 1158 * This ID is used to make future calls related to this download. 1159 */ 1160 public long addCompletedDownload(String title, String description, 1161 boolean isMediaScannerScannable, String mimeType, String path, long length, 1162 boolean showNotification) { 1163 // make sure the input args are non-null/non-zero 1164 validateArgumentIsNonEmpty("title", title); 1165 validateArgumentIsNonEmpty("description", description); 1166 validateArgumentIsNonEmpty("path", path); 1167 validateArgumentIsNonEmpty("mimeType", mimeType); 1168 if (length < 0) { 1169 throw new IllegalArgumentException(" invalid value for param: totalBytes"); 1170 } 1171 1172 // if there is already an entry with the given path name in downloads.db, return its id 1173 Request request = new Request(NON_DOWNLOADMANAGER_DOWNLOAD) 1174 .setTitle(title) 1175 .setDescription(description) 1176 .setMimeType(mimeType); 1177 ContentValues values = request.toContentValues(null); 1178 values.put(Downloads.Impl.COLUMN_DESTINATION, 1179 Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD); 1180 values.put(Downloads.Impl._DATA, path); 1181 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS); 1182 values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, length); 1183 values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED, 1184 (isMediaScannerScannable) ? Request.SCANNABLE_VALUE_YES : 1185 Request.SCANNABLE_VALUE_NO); 1186 values.put(Downloads.Impl.COLUMN_VISIBILITY, (showNotification) ? 1187 Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION : Request.VISIBILITY_HIDDEN); 1188 Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values); 1189 if (downloadUri == null) { 1190 return -1; 1191 } 1192 return Long.parseLong(downloadUri.getLastPathSegment()); 1193 } 1194 private static final String NON_DOWNLOADMANAGER_DOWNLOAD = 1195 "non-dwnldmngr-download-dont-retry2download"; 1196 1197 private static void validateArgumentIsNonEmpty(String paramName, String val) { 1198 if (TextUtils.isEmpty(val)) { 1199 throw new IllegalArgumentException(paramName + " can't be null"); 1200 } 1201 } 1202 1203 /** 1204 * Get the DownloadProvider URI for the download with the given ID. 1205 */ 1206 Uri getDownloadUri(long id) { 1207 return ContentUris.withAppendedId(mBaseUri, id); 1208 } 1209 1210 /** 1211 * Get a parameterized SQL WHERE clause to select a bunch of IDs. 1212 */ 1213 static String getWhereClauseForIds(long[] ids) { 1214 StringBuilder whereClause = new StringBuilder(); 1215 whereClause.append("("); 1216 for (int i = 0; i < ids.length; i++) { 1217 if (i > 0) { 1218 whereClause.append("OR "); 1219 } 1220 whereClause.append(Downloads.Impl._ID); 1221 whereClause.append(" = ? "); 1222 } 1223 whereClause.append(")"); 1224 return whereClause.toString(); 1225 } 1226 1227 /** 1228 * Get the selection args for a clause returned by {@link #getWhereClauseForIds(long[])}. 1229 */ 1230 static String[] getWhereArgsForIds(long[] ids) { 1231 String[] whereArgs = new String[ids.length]; 1232 for (int i = 0; i < ids.length; i++) { 1233 whereArgs[i] = Long.toString(ids[i]); 1234 } 1235 return whereArgs; 1236 } 1237 1238 /** 1239 * This class wraps a cursor returned by DownloadProvider -- the "underlying cursor" -- and 1240 * presents a different set of columns, those defined in the DownloadManager.COLUMN_* constants. 1241 * Some columns correspond directly to underlying values while others are computed from 1242 * underlying data. 1243 */ 1244 private static class CursorTranslator extends CursorWrapper { 1245 private Uri mBaseUri; 1246 1247 public CursorTranslator(Cursor cursor, Uri baseUri) { 1248 super(cursor); 1249 mBaseUri = baseUri; 1250 } 1251 1252 @Override 1253 public int getInt(int columnIndex) { 1254 return (int) getLong(columnIndex); 1255 } 1256 1257 @Override 1258 public long getLong(int columnIndex) { 1259 if (getColumnName(columnIndex).equals(COLUMN_REASON)) { 1260 return getReason(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS))); 1261 } else if (getColumnName(columnIndex).equals(COLUMN_STATUS)) { 1262 return translateStatus(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS))); 1263 } else { 1264 return super.getLong(columnIndex); 1265 } 1266 } 1267 1268 @Override 1269 public String getString(int columnIndex) { 1270 return (getColumnName(columnIndex).equals(COLUMN_LOCAL_URI)) ? getLocalUri() : 1271 super.getString(columnIndex); 1272 } 1273 1274 private String getLocalUri() { 1275 long destinationType = getLong(getColumnIndex(Downloads.Impl.COLUMN_DESTINATION)); 1276 if (destinationType == Downloads.Impl.DESTINATION_FILE_URI || 1277 destinationType == Downloads.Impl.DESTINATION_EXTERNAL || 1278 destinationType == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) { 1279 String localPath = getString(getColumnIndex(COLUMN_LOCAL_FILENAME)); 1280 if (localPath == null) { 1281 return null; 1282 } 1283 return Uri.fromFile(new File(localPath)).toString(); 1284 } 1285 1286 // return content URI for cache download 1287 long downloadId = getLong(getColumnIndex(Downloads.Impl._ID)); 1288 return ContentUris.withAppendedId(mBaseUri, downloadId).toString(); 1289 } 1290 1291 private long getReason(int status) { 1292 switch (translateStatus(status)) { 1293 case STATUS_FAILED: 1294 return getErrorCode(status); 1295 1296 case STATUS_PAUSED: 1297 return getPausedReason(status); 1298 1299 default: 1300 return 0; // arbitrary value when status is not an error 1301 } 1302 } 1303 1304 private long getPausedReason(int status) { 1305 switch (status) { 1306 case Downloads.Impl.STATUS_WAITING_TO_RETRY: 1307 return PAUSED_WAITING_TO_RETRY; 1308 1309 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK: 1310 return PAUSED_WAITING_FOR_NETWORK; 1311 1312 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI: 1313 return PAUSED_QUEUED_FOR_WIFI; 1314 1315 default: 1316 return PAUSED_UNKNOWN; 1317 } 1318 } 1319 1320 private long getErrorCode(int status) { 1321 if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS) 1322 || (500 <= status && status < 600)) { 1323 // HTTP status code 1324 return status; 1325 } 1326 1327 switch (status) { 1328 case Downloads.Impl.STATUS_FILE_ERROR: 1329 return ERROR_FILE_ERROR; 1330 1331 case Downloads.Impl.STATUS_UNHANDLED_HTTP_CODE: 1332 case Downloads.Impl.STATUS_UNHANDLED_REDIRECT: 1333 return ERROR_UNHANDLED_HTTP_CODE; 1334 1335 case Downloads.Impl.STATUS_HTTP_DATA_ERROR: 1336 return ERROR_HTTP_DATA_ERROR; 1337 1338 case Downloads.Impl.STATUS_TOO_MANY_REDIRECTS: 1339 return ERROR_TOO_MANY_REDIRECTS; 1340 1341 case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR: 1342 return ERROR_INSUFFICIENT_SPACE; 1343 1344 case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR: 1345 return ERROR_DEVICE_NOT_FOUND; 1346 1347 case Downloads.Impl.STATUS_CANNOT_RESUME: 1348 return ERROR_CANNOT_RESUME; 1349 1350 case Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR: 1351 return ERROR_FILE_ALREADY_EXISTS; 1352 1353 default: 1354 return ERROR_UNKNOWN; 1355 } 1356 } 1357 1358 private int translateStatus(int status) { 1359 switch (status) { 1360 case Downloads.Impl.STATUS_PENDING: 1361 return STATUS_PENDING; 1362 1363 case Downloads.Impl.STATUS_RUNNING: 1364 return STATUS_RUNNING; 1365 1366 case Downloads.Impl.STATUS_PAUSED_BY_APP: 1367 case Downloads.Impl.STATUS_WAITING_TO_RETRY: 1368 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK: 1369 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI: 1370 return STATUS_PAUSED; 1371 1372 case Downloads.Impl.STATUS_SUCCESS: 1373 return STATUS_SUCCESSFUL; 1374 1375 default: 1376 assert Downloads.Impl.isStatusError(status); 1377 return STATUS_FAILED; 1378 } 1379 } 1380 } 1381 } 1382