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