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