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