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 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 * @param uri a file {@link Uri} indicating the destination for the downloaded file. 497 * @return this object 498 */ setDestinationUri(Uri uri)499 public Request setDestinationUri(Uri uri) { 500 mDestinationUri = uri; 501 return this; 502 } 503 504 /** 505 * Set the local destination for the downloaded file to a path within 506 * the application's external files directory (as returned by 507 * {@link Context#getExternalFilesDir(String)}. 508 * <p> 509 * The downloaded file is not scanned by MediaScanner. But it can be 510 * made scannable by calling {@link #allowScanningByMediaScanner()}. 511 * 512 * @param context the {@link Context} to use in determining the external 513 * files directory 514 * @param dirType the directory type to pass to 515 * {@link Context#getExternalFilesDir(String)} 516 * @param subPath the path within the external directory, including the 517 * destination filename 518 * @return this object 519 * @throws IllegalStateException If the external storage directory 520 * cannot be found or created. 521 */ setDestinationInExternalFilesDir(Context context, String dirType, String subPath)522 public Request setDestinationInExternalFilesDir(Context context, String dirType, 523 String subPath) { 524 final File file = context.getExternalFilesDir(dirType); 525 if (file == null) { 526 throw new IllegalStateException("Failed to get external storage files directory"); 527 } else if (file.exists()) { 528 if (!file.isDirectory()) { 529 throw new IllegalStateException(file.getAbsolutePath() + 530 " already exists and is not a directory"); 531 } 532 } else { 533 if (!file.mkdirs()) { 534 throw new IllegalStateException("Unable to create directory: "+ 535 file.getAbsolutePath()); 536 } 537 } 538 setDestinationFromBase(file, subPath); 539 return this; 540 } 541 542 /** 543 * Set the local destination for the downloaded file to a path within 544 * the public external storage directory (as returned by 545 * {@link Environment#getExternalStoragePublicDirectory(String)}). 546 * <p> 547 * The downloaded file is not scanned by MediaScanner. But it can be 548 * made scannable by calling {@link #allowScanningByMediaScanner()}. 549 * 550 * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above, 551 * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE} 552 * permission is not needed and the {@code dirType} must be one of the known public 553 * directories like {@link Environment#DIRECTORY_DOWNLOADS}, 554 * {@link Environment#DIRECTORY_PICTURES}, {@link Environment#DIRECTORY_MOVIES}, etc. 555 * 556 * @param dirType the directory type to pass to {@link Environment#getExternalStoragePublicDirectory(String)} 557 * @param subPath the path within the external directory, including the 558 * destination filename 559 * @return this object 560 * @throws IllegalStateException If the external storage directory 561 * cannot be found or created. 562 */ setDestinationInExternalPublicDir(String dirType, String subPath)563 public Request setDestinationInExternalPublicDir(String dirType, String subPath) { 564 File file = Environment.getExternalStoragePublicDirectory(dirType); 565 if (file == null) { 566 throw new IllegalStateException("Failed to get external storage public directory"); 567 } 568 569 final Context context = AppGlobals.getInitialApplication(); 570 if (context.getApplicationInfo().targetSdkVersion 571 >= Build.VERSION_CODES.Q || !Environment.isExternalStorageLegacy()) { 572 try (ContentProviderClient client = context.getContentResolver() 573 .acquireContentProviderClient(Downloads.Impl.AUTHORITY)) { 574 final Bundle extras = new Bundle(); 575 extras.putString(Downloads.DIR_TYPE, dirType); 576 client.call(Downloads.CALL_CREATE_EXTERNAL_PUBLIC_DIR, null, extras); 577 } catch (RemoteException e) { 578 throw new IllegalStateException("Unable to create directory: " 579 + file.getAbsolutePath()); 580 } 581 } else { 582 if (file.exists()) { 583 if (!file.isDirectory()) { 584 throw new IllegalStateException(file.getAbsolutePath() 585 + " already exists and is not a directory"); 586 } 587 } else if (!file.mkdirs()) { 588 throw new IllegalStateException("Unable to create directory: " 589 + file.getAbsolutePath()); 590 } 591 } 592 setDestinationFromBase(file, subPath); 593 return this; 594 } 595 setDestinationFromBase(File base, String subPath)596 private void setDestinationFromBase(File base, String subPath) { 597 if (subPath == null) { 598 throw new NullPointerException("subPath cannot be null"); 599 } 600 mDestinationUri = Uri.withAppendedPath(Uri.fromFile(base), subPath); 601 } 602 603 /** 604 * If the file to be downloaded is to be scanned by MediaScanner, this method 605 * should be called before {@link DownloadManager#enqueue(Request)} is called. 606 * 607 * @deprecated Starting in Q, this value is ignored. Files downloaded to 608 * directories owned by applications (e.g. {@link Context#getExternalFilesDir(String)}) 609 * will not be scanned by MediaScanner and the rest will be scanned. 610 */ 611 @Deprecated allowScanningByMediaScanner()612 public void allowScanningByMediaScanner() { 613 mScannable = true; 614 } 615 616 /** 617 * Add an HTTP header to be included with the download request. The header will be added to 618 * the end of the list. 619 * @param header HTTP header name 620 * @param value header value 621 * @return this object 622 * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">HTTP/1.1 623 * Message Headers</a> 624 */ addRequestHeader(String header, String value)625 public Request addRequestHeader(String header, String value) { 626 if (header == null) { 627 throw new NullPointerException("header cannot be null"); 628 } 629 if (header.contains(":")) { 630 throw new IllegalArgumentException("header may not contain ':'"); 631 } 632 if (value == null) { 633 value = ""; 634 } 635 mRequestHeaders.add(Pair.create(header, value)); 636 return this; 637 } 638 639 /** 640 * Set the title of this download, to be displayed in notifications (if enabled). If no 641 * title is given, a default one will be assigned based on the download filename, once the 642 * download starts. 643 * @return this object 644 */ setTitle(CharSequence title)645 public Request setTitle(CharSequence title) { 646 mTitle = title; 647 return this; 648 } 649 650 /** 651 * Set a description of this download, to be displayed in notifications (if enabled) 652 * @return this object 653 */ setDescription(CharSequence description)654 public Request setDescription(CharSequence description) { 655 mDescription = description; 656 return this; 657 } 658 659 /** 660 * Set the MIME content type of this download. This will override the content type declared 661 * in the server's response. 662 * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7">HTTP/1.1 663 * Media Types</a> 664 * @return this object 665 */ setMimeType(String mimeType)666 public Request setMimeType(String mimeType) { 667 mMimeType = mimeType; 668 return this; 669 } 670 671 /** 672 * Control whether a system notification is posted by the download manager while this 673 * download is running. If enabled, the download manager posts notifications about downloads 674 * through the system {@link android.app.NotificationManager}. By default, a notification is 675 * shown. 676 * 677 * If set to false, this requires the permission 678 * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION. 679 * 680 * @param show whether the download manager should show a notification for this download. 681 * @return this object 682 * @deprecated use {@link #setNotificationVisibility(int)} 683 */ 684 @Deprecated setShowRunningNotification(boolean show)685 public Request setShowRunningNotification(boolean show) { 686 return (show) ? setNotificationVisibility(VISIBILITY_VISIBLE) : 687 setNotificationVisibility(VISIBILITY_HIDDEN); 688 } 689 690 /** 691 * Control whether a system notification is posted by the download manager while this 692 * download is running or when it is completed. 693 * If enabled, the download manager posts notifications about downloads 694 * through the system {@link android.app.NotificationManager}. 695 * By default, a notification is shown only when the download is in progress. 696 *<p> 697 * It can take the following values: {@link #VISIBILITY_HIDDEN}, 698 * {@link #VISIBILITY_VISIBLE}, 699 * {@link #VISIBILITY_VISIBLE_NOTIFY_COMPLETED}. 700 *<p> 701 * If set to {@link #VISIBILITY_HIDDEN}, this requires the permission 702 * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION. 703 * 704 * @param visibility the visibility setting value 705 * @return this object 706 */ setNotificationVisibility(int visibility)707 public Request setNotificationVisibility(int visibility) { 708 mNotificationVisibility = visibility; 709 return this; 710 } 711 712 /** 713 * Restrict the types of networks over which this download may proceed. 714 * By default, all network types are allowed. Consider using 715 * {@link #setAllowedOverMetered(boolean)} instead, since it's more 716 * flexible. 717 * <p> 718 * As of {@link android.os.Build.VERSION_CODES#N}, setting only the 719 * {@link #NETWORK_WIFI} flag here is equivalent to calling 720 * {@link #setAllowedOverMetered(boolean)} with {@code false}. 721 * 722 * @param flags any combination of the NETWORK_* bit flags. 723 * @return this object 724 */ setAllowedNetworkTypes(int flags)725 public Request setAllowedNetworkTypes(int flags) { 726 mAllowedNetworkTypes = flags; 727 return this; 728 } 729 730 /** 731 * Set whether this download may proceed over a roaming connection. By default, roaming is 732 * allowed. 733 * @param allowed whether to allow a roaming connection to be used 734 * @return this object 735 */ setAllowedOverRoaming(boolean allowed)736 public Request setAllowedOverRoaming(boolean allowed) { 737 mRoamingAllowed = allowed; 738 return this; 739 } 740 741 /** 742 * Set whether this download may proceed over a metered network 743 * connection. By default, metered networks are allowed. 744 * 745 * @see ConnectivityManager#isActiveNetworkMetered() 746 */ setAllowedOverMetered(boolean allow)747 public Request setAllowedOverMetered(boolean allow) { 748 mMeteredAllowed = allow; 749 return this; 750 } 751 752 /** 753 * Specify that to run this download, the device needs to be plugged in. 754 * This defaults to false. 755 * 756 * @param requiresCharging Whether or not the device is plugged in. 757 * @see android.app.job.JobInfo.Builder#setRequiresCharging(boolean) 758 */ setRequiresCharging(boolean requiresCharging)759 public Request setRequiresCharging(boolean requiresCharging) { 760 if (requiresCharging) { 761 mFlags |= Downloads.Impl.FLAG_REQUIRES_CHARGING; 762 } else { 763 mFlags &= ~Downloads.Impl.FLAG_REQUIRES_CHARGING; 764 } 765 return this; 766 } 767 768 /** 769 * Specify that to run, the download needs the device to be in idle 770 * mode. This defaults to false. 771 * <p> 772 * Idle mode is a loose definition provided by the system, which means 773 * that the device is not in use, and has not been in use for some time. 774 * 775 * @param requiresDeviceIdle Whether or not the device need be within an 776 * idle maintenance window. 777 * @see android.app.job.JobInfo.Builder#setRequiresDeviceIdle(boolean) 778 */ setRequiresDeviceIdle(boolean requiresDeviceIdle)779 public Request setRequiresDeviceIdle(boolean requiresDeviceIdle) { 780 if (requiresDeviceIdle) { 781 mFlags |= Downloads.Impl.FLAG_REQUIRES_DEVICE_IDLE; 782 } else { 783 mFlags &= ~Downloads.Impl.FLAG_REQUIRES_DEVICE_IDLE; 784 } 785 return this; 786 } 787 788 /** 789 * Set whether this download should be displayed in the system's Downloads UI. True by 790 * default. 791 * @param isVisible whether to display this download in the Downloads UI 792 * @return this object 793 * 794 * @deprecated Starting in Q, this value is ignored. Only files downloaded to 795 * public Downloads directory (as returned by 796 * {@link Environment#getExternalStoragePublicDirectory(String)} with 797 * {@link Environment#DIRECTORY_DOWNLOADS}) will be visible in system's Downloads UI 798 * and the rest will not be visible. 799 * (e.g. {@link Context#getExternalFilesDir(String)}) will not be visible. 800 */ 801 @Deprecated setVisibleInDownloadsUi(boolean isVisible)802 public Request setVisibleInDownloadsUi(boolean isVisible) { 803 mIsVisibleInDownloadsUi = isVisible; 804 return this; 805 } 806 807 /** 808 * @return ContentValues to be passed to DownloadProvider.insert() 809 */ toContentValues(String packageName)810 ContentValues toContentValues(String packageName) { 811 ContentValues values = new ContentValues(); 812 assert mUri != null; 813 values.put(Downloads.Impl.COLUMN_URI, mUri.toString()); 814 values.put(Downloads.Impl.COLUMN_IS_PUBLIC_API, true); 815 values.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, packageName); 816 817 if (mDestinationUri != null) { 818 values.put(Downloads.Impl.COLUMN_DESTINATION, 819 Downloads.Impl.DESTINATION_FILE_URI); 820 values.put(Downloads.Impl.COLUMN_FILE_NAME_HINT, 821 mDestinationUri.toString()); 822 } else { 823 values.put(Downloads.Impl.COLUMN_DESTINATION, 824 Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE); 825 } 826 // is the file supposed to be media-scannable? 827 values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED, (mScannable) ? SCANNABLE_VALUE_YES : 828 SCANNABLE_VALUE_NO); 829 830 if (!mRequestHeaders.isEmpty()) { 831 encodeHttpHeaders(values); 832 } 833 834 putIfNonNull(values, Downloads.Impl.COLUMN_TITLE, mTitle); 835 putIfNonNull(values, Downloads.Impl.COLUMN_DESCRIPTION, mDescription); 836 putIfNonNull(values, Downloads.Impl.COLUMN_MIME_TYPE, mMimeType); 837 838 values.put(Downloads.Impl.COLUMN_VISIBILITY, mNotificationVisibility); 839 values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes); 840 values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed); 841 values.put(Downloads.Impl.COLUMN_ALLOW_METERED, mMeteredAllowed); 842 values.put(Downloads.Impl.COLUMN_FLAGS, mFlags); 843 values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, mIsVisibleInDownloadsUi); 844 845 return values; 846 } 847 encodeHttpHeaders(ContentValues values)848 private void encodeHttpHeaders(ContentValues values) { 849 int index = 0; 850 for (Pair<String, String> header : mRequestHeaders) { 851 String headerString = header.first + ": " + header.second; 852 values.put(Downloads.Impl.RequestHeaders.INSERT_KEY_PREFIX + index, headerString); 853 index++; 854 } 855 } 856 putIfNonNull(ContentValues contentValues, String key, Object value)857 private void putIfNonNull(ContentValues contentValues, String key, Object value) { 858 if (value != null) { 859 contentValues.put(key, value.toString()); 860 } 861 } 862 } 863 864 /** 865 * This class may be used to filter download manager queries. 866 */ 867 public static class Query { 868 /** 869 * Constant for use with {@link #orderBy} 870 * @hide 871 */ 872 public static final int ORDER_ASCENDING = 1; 873 874 /** 875 * Constant for use with {@link #orderBy} 876 * @hide 877 */ 878 public static final int ORDER_DESCENDING = 2; 879 880 private long[] mIds = null; 881 private Integer mStatusFlags = null; 882 private String mFilterString = null; 883 private String mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION; 884 private int mOrderDirection = ORDER_DESCENDING; 885 private boolean mOnlyIncludeVisibleInDownloadsUi = false; 886 887 /** 888 * Include only the downloads with the given IDs. 889 * @return this object 890 */ setFilterById(long... ids)891 public Query setFilterById(long... ids) { 892 mIds = ids; 893 return this; 894 } 895 896 /** 897 * 898 * Include only the downloads that contains the given string in its name. 899 * @return this object 900 * @hide 901 */ setFilterByString(@ullable String filter)902 public Query setFilterByString(@Nullable String filter) { 903 mFilterString = filter; 904 return this; 905 } 906 907 /** 908 * Include only downloads with status matching any the given status flags. 909 * @param flags any combination of the STATUS_* bit flags 910 * @return this object 911 */ setFilterByStatus(int flags)912 public Query setFilterByStatus(int flags) { 913 mStatusFlags = flags; 914 return this; 915 } 916 917 /** 918 * Controls whether this query includes downloads not visible in the system's Downloads UI. 919 * @param value if true, this query will only include downloads that should be displayed in 920 * the system's Downloads UI; if false (the default), this query will include 921 * both visible and invisible downloads. 922 * @return this object 923 * @hide 924 */ 925 @UnsupportedAppUsage setOnlyIncludeVisibleInDownloadsUi(boolean value)926 public Query setOnlyIncludeVisibleInDownloadsUi(boolean value) { 927 mOnlyIncludeVisibleInDownloadsUi = value; 928 return this; 929 } 930 931 /** 932 * Change the sort order of the returned Cursor. 933 * 934 * @param column one of the COLUMN_* constants; currently, only 935 * {@link #COLUMN_LAST_MODIFIED_TIMESTAMP} and {@link #COLUMN_TOTAL_SIZE_BYTES} are 936 * supported. 937 * @param direction either {@link #ORDER_ASCENDING} or {@link #ORDER_DESCENDING} 938 * @return this object 939 * @hide 940 */ 941 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) orderBy(String column, int direction)942 public Query orderBy(String column, int direction) { 943 if (direction != ORDER_ASCENDING && direction != ORDER_DESCENDING) { 944 throw new IllegalArgumentException("Invalid direction: " + direction); 945 } 946 947 if (column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP)) { 948 mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION; 949 } else if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) { 950 mOrderByColumn = Downloads.Impl.COLUMN_TOTAL_BYTES; 951 } else { 952 throw new IllegalArgumentException("Cannot order by " + column); 953 } 954 mOrderDirection = direction; 955 return this; 956 } 957 958 /** 959 * Run this query using the given ContentResolver. 960 * @param projection the projection to pass to ContentResolver.query() 961 * @return the Cursor returned by ContentResolver.query() 962 */ runQuery(ContentResolver resolver, String[] projection, Uri baseUri)963 Cursor runQuery(ContentResolver resolver, String[] projection, Uri baseUri) { 964 Uri uri = baseUri; 965 List<String> selectionParts = new ArrayList<String>(); 966 String[] selectionArgs = null; 967 968 int whereArgsCount = (mIds == null) ? 0 : mIds.length; 969 whereArgsCount = (mFilterString == null) ? whereArgsCount : whereArgsCount + 1; 970 selectionArgs = new String[whereArgsCount]; 971 972 if (whereArgsCount > 0) { 973 if (mIds != null) { 974 selectionParts.add(getWhereClauseForIds(mIds)); 975 getWhereArgsForIds(mIds, selectionArgs); 976 } 977 978 if (mFilterString != null) { 979 selectionParts.add(Downloads.Impl.COLUMN_TITLE + " LIKE ?"); 980 selectionArgs[selectionArgs.length - 1] = "%" + mFilterString + "%"; 981 } 982 } 983 984 if (mStatusFlags != null) { 985 List<String> parts = new ArrayList<String>(); 986 if ((mStatusFlags & STATUS_PENDING) != 0) { 987 parts.add(statusClause("=", Downloads.Impl.STATUS_PENDING)); 988 } 989 if ((mStatusFlags & STATUS_RUNNING) != 0) { 990 parts.add(statusClause("=", Downloads.Impl.STATUS_RUNNING)); 991 } 992 if ((mStatusFlags & STATUS_PAUSED) != 0) { 993 parts.add(statusClause("=", Downloads.Impl.STATUS_PAUSED_BY_APP)); 994 parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_TO_RETRY)); 995 parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_FOR_NETWORK)); 996 parts.add(statusClause("=", Downloads.Impl.STATUS_QUEUED_FOR_WIFI)); 997 } 998 if ((mStatusFlags & STATUS_SUCCESSFUL) != 0) { 999 parts.add(statusClause("=", Downloads.Impl.STATUS_SUCCESS)); 1000 } 1001 if ((mStatusFlags & STATUS_FAILED) != 0) { 1002 parts.add("(" + statusClause(">=", 400) 1003 + " AND " + statusClause("<", 600) + ")"); 1004 } 1005 selectionParts.add(joinStrings(" OR ", parts)); 1006 } 1007 1008 if (mOnlyIncludeVisibleInDownloadsUi) { 1009 selectionParts.add(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI + " != '0'"); 1010 } 1011 1012 // only return rows which are not marked 'deleted = 1' 1013 selectionParts.add(Downloads.Impl.COLUMN_DELETED + " != '1'"); 1014 1015 String selection = joinStrings(" AND ", selectionParts); 1016 String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC"); 1017 String orderBy = mOrderByColumn + " " + orderDirection; 1018 1019 return resolver.query(uri, projection, selection, selectionArgs, orderBy); 1020 } 1021 joinStrings(String joiner, Iterable<String> parts)1022 private String joinStrings(String joiner, Iterable<String> parts) { 1023 StringBuilder builder = new StringBuilder(); 1024 boolean first = true; 1025 for (String part : parts) { 1026 if (!first) { 1027 builder.append(joiner); 1028 } 1029 builder.append(part); 1030 first = false; 1031 } 1032 return builder.toString(); 1033 } 1034 statusClause(String operator, int value)1035 private String statusClause(String operator, int value) { 1036 return Downloads.Impl.COLUMN_STATUS + operator + "'" + value + "'"; 1037 } 1038 } 1039 1040 private final ContentResolver mResolver; 1041 private final String mPackageName; 1042 1043 private Uri mBaseUri = Downloads.Impl.CONTENT_URI; 1044 private boolean mAccessFilename; 1045 1046 /** 1047 * @hide 1048 */ DownloadManager(Context context)1049 public DownloadManager(Context context) { 1050 mResolver = context.getContentResolver(); 1051 mPackageName = context.getPackageName(); 1052 1053 // Callers can access filename columns when targeting old platform 1054 // versions; otherwise we throw telling them it's deprecated. 1055 mAccessFilename = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.N; 1056 } 1057 1058 /** 1059 * Makes this object access the download provider through /all_downloads URIs rather than 1060 * /my_downloads URIs, for clients that have permission to do so. 1061 * @hide 1062 */ 1063 @UnsupportedAppUsage 1064 public void setAccessAllDownloads(boolean accessAllDownloads) { 1065 if (accessAllDownloads) { 1066 mBaseUri = Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI; 1067 } else { 1068 mBaseUri = Downloads.Impl.CONTENT_URI; 1069 } 1070 } 1071 1072 /** {@hide} */ 1073 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) 1074 public void setAccessFilename(boolean accessFilename) { 1075 mAccessFilename = accessFilename; 1076 } 1077 1078 /** 1079 * Notify {@link DownloadManager} that the given {@link MediaStore} items 1080 * were just deleted so that {@link DownloadManager} internal data 1081 * structures can be cleaned up. 1082 * 1083 * @param idToMime map from {@link BaseColumns#_ID} to 1084 * {@link ContentResolver#getType(Uri)}. 1085 * @hide 1086 */ 1087 @SystemApi 1088 @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) 1089 public void onMediaStoreDownloadsDeleted(@NonNull LongSparseArray<String> idToMime) { 1090 try (ContentProviderClient client = mResolver 1091 .acquireUnstableContentProviderClient(mBaseUri)) { 1092 final Bundle callExtras = new Bundle(); 1093 final long[] ids = new long[idToMime.size()]; 1094 final String[] mimeTypes = new String[idToMime.size()]; 1095 for (int i = idToMime.size() - 1; i >= 0; --i) { 1096 ids[i] = idToMime.keyAt(i); 1097 mimeTypes[i] = idToMime.valueAt(i); 1098 } 1099 callExtras.putLongArray(android.provider.Downloads.EXTRA_IDS, ids); 1100 callExtras.putStringArray(android.provider.Downloads.EXTRA_MIME_TYPES, 1101 mimeTypes); 1102 client.call(android.provider.Downloads.CALL_MEDIASTORE_DOWNLOADS_DELETED, 1103 null, callExtras); 1104 } catch (RemoteException e) { 1105 // Should not happen 1106 } 1107 } 1108 1109 /** 1110 * Enqueue a new download. The download will start automatically once the download manager is 1111 * ready to execute it and connectivity is available. 1112 * 1113 * @param request the parameters specifying this download 1114 * @return an ID for the download, unique across the system. This ID is used to make future 1115 * calls related to this download. 1116 */ enqueue(Request request)1117 public long enqueue(Request request) { 1118 ContentValues values = request.toContentValues(mPackageName); 1119 Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values); 1120 long id = Long.parseLong(downloadUri.getLastPathSegment()); 1121 return id; 1122 } 1123 1124 /** 1125 * Marks the specified download as 'to be deleted'. This is done when a completed download 1126 * is to be removed but the row was stored without enough info to delete the corresponding 1127 * metadata from Mediaprovider database. Actual cleanup of this row is done in DownloadService. 1128 * 1129 * @param ids the IDs of the downloads to be marked 'deleted' 1130 * @return the number of downloads actually updated 1131 * @hide 1132 */ markRowDeleted(long... ids)1133 public int markRowDeleted(long... ids) { 1134 if (ids == null || ids.length == 0) { 1135 // called with nothing to remove! 1136 throw new IllegalArgumentException("input param 'ids' can't be null"); 1137 } 1138 return mResolver.delete(mBaseUri, getWhereClauseForIds(ids), getWhereArgsForIds(ids)); 1139 } 1140 1141 /** 1142 * Cancel downloads and remove them from the download manager. Each download will be stopped if 1143 * it was running, and it will no longer be accessible through the download manager. 1144 * If there is a downloaded file, partial or complete, it is deleted. 1145 * 1146 * @param ids the IDs of the downloads to remove 1147 * @return the number of downloads actually removed 1148 */ remove(long... ids)1149 public int remove(long... ids) { 1150 return markRowDeleted(ids); 1151 } 1152 1153 /** 1154 * Query the download manager about downloads that have been requested. 1155 * @param query parameters specifying filters for this query 1156 * @return a Cursor over the result set of downloads, with columns consisting of all the 1157 * COLUMN_* constants. 1158 */ query(Query query)1159 public Cursor query(Query query) { 1160 return query(query, UNDERLYING_COLUMNS); 1161 } 1162 1163 /** @hide */ query(Query query, String[] projection)1164 public Cursor query(Query query, String[] projection) { 1165 Cursor underlyingCursor = query.runQuery(mResolver, projection, mBaseUri); 1166 if (underlyingCursor == null) { 1167 return null; 1168 } 1169 return new CursorTranslator(underlyingCursor, mBaseUri, mAccessFilename); 1170 } 1171 1172 /** 1173 * Open a downloaded file for reading. The download must have completed. 1174 * @param id the ID of the download 1175 * @return a read-only {@link ParcelFileDescriptor} 1176 * @throws FileNotFoundException if the destination file does not already exist 1177 */ openDownloadedFile(long id)1178 public ParcelFileDescriptor openDownloadedFile(long id) throws FileNotFoundException { 1179 return mResolver.openFileDescriptor(getDownloadUri(id), "r"); 1180 } 1181 1182 /** 1183 * Returns the {@link Uri} of the given downloaded file id, if the file is 1184 * downloaded successfully. Otherwise, null is returned. 1185 * 1186 * @param id the id of the downloaded file. 1187 * @return the {@link Uri} of the given downloaded file id, if download was 1188 * successful. null otherwise. 1189 */ getUriForDownloadedFile(long id)1190 public Uri getUriForDownloadedFile(long id) { 1191 // to check if the file is in cache, get its destination from the database 1192 Query query = new Query().setFilterById(id); 1193 Cursor cursor = null; 1194 try { 1195 cursor = query(query); 1196 if (cursor == null) { 1197 return null; 1198 } 1199 if (cursor.moveToFirst()) { 1200 int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS)); 1201 if (DownloadManager.STATUS_SUCCESSFUL == status) { 1202 return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id); 1203 } 1204 } 1205 } finally { 1206 if (cursor != null) { 1207 cursor.close(); 1208 } 1209 } 1210 // downloaded file not found or its status is not 'successfully completed' 1211 return null; 1212 } 1213 1214 /** 1215 * Returns the media type of the given downloaded file id, if the file was 1216 * downloaded successfully. Otherwise, null is returned. 1217 * 1218 * @param id the id of the downloaded file. 1219 * @return the media type of the given downloaded file id, if download was successful. null 1220 * otherwise. 1221 */ getMimeTypeForDownloadedFile(long id)1222 public String getMimeTypeForDownloadedFile(long id) { 1223 Query query = new Query().setFilterById(id); 1224 Cursor cursor = null; 1225 try { 1226 cursor = query(query); 1227 if (cursor == null) { 1228 return null; 1229 } 1230 while (cursor.moveToFirst()) { 1231 return cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_MEDIA_TYPE)); 1232 } 1233 } finally { 1234 if (cursor != null) { 1235 cursor.close(); 1236 } 1237 } 1238 // downloaded file not found or its status is not 'successfully completed' 1239 return null; 1240 } 1241 1242 /** 1243 * Restart the given downloads, which must have already completed (successfully or not). This 1244 * method will only work when called from within the download manager's process. 1245 * @param ids the IDs of the downloads 1246 * @hide 1247 */ 1248 @UnsupportedAppUsage restartDownload(long... ids)1249 public void restartDownload(long... ids) { 1250 Cursor cursor = query(new Query().setFilterById(ids)); 1251 try { 1252 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { 1253 int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS)); 1254 if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) { 1255 throw new IllegalArgumentException("Cannot restart incomplete download: " 1256 + cursor.getLong(cursor.getColumnIndex(COLUMN_ID))); 1257 } 1258 } 1259 } finally { 1260 cursor.close(); 1261 } 1262 1263 ContentValues values = new ContentValues(); 1264 values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0); 1265 values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1); 1266 values.putNull(Downloads.Impl._DATA); 1267 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING); 1268 values.put(Downloads.Impl.COLUMN_FAILED_CONNECTIONS, 0); 1269 mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids)); 1270 } 1271 1272 /** 1273 * Force the given downloads to proceed even if their size is larger than 1274 * {@link #getMaxBytesOverMobile(Context)}. 1275 * 1276 * @hide 1277 */ forceDownload(long... ids)1278 public void forceDownload(long... ids) { 1279 ContentValues values = new ContentValues(); 1280 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING); 1281 values.put(Downloads.Impl.COLUMN_CONTROL, Downloads.Impl.CONTROL_RUN); 1282 values.put(Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT, 1); 1283 mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids)); 1284 } 1285 1286 /** 1287 * Returns maximum size, in bytes, of downloads that may go over a mobile connection; or null if 1288 * there's no limit 1289 * 1290 * @param context the {@link Context} to use for accessing the {@link ContentResolver} 1291 * @return maximum size, in bytes, of downloads that may go over a mobile connection; or null if 1292 * there's no limit 1293 */ getMaxBytesOverMobile(Context context)1294 public static Long getMaxBytesOverMobile(Context context) { 1295 try { 1296 return Settings.Global.getLong(context.getContentResolver(), 1297 Settings.Global.DOWNLOAD_MAX_BYTES_OVER_MOBILE); 1298 } catch (SettingNotFoundException exc) { 1299 return null; 1300 } 1301 } 1302 1303 /** 1304 * Rename the given download if the download has completed 1305 * 1306 * @param context the {@link Context} to use in case need to update MediaProvider 1307 * @param id the downloaded id 1308 * @param displayName the new name to rename to 1309 * @return true if rename was successful, false otherwise 1310 * @hide 1311 */ rename(Context context, long id, String displayName)1312 public boolean rename(Context context, long id, String displayName) { 1313 if (!FileUtils.isValidFatFilename(displayName)) { 1314 throw new SecurityException(displayName + " is not a valid filename"); 1315 } 1316 1317 final String filePath; 1318 final Query query = new Query().setFilterById(id); 1319 try (Cursor cursor = query(query)) { 1320 if (cursor == null) { 1321 throw new IllegalStateException("Missing cursor for download id=" + id); 1322 } 1323 if (cursor.moveToFirst()) { 1324 final int status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS)); 1325 if (status != DownloadManager.STATUS_SUCCESSFUL) { 1326 throw new IllegalStateException("Download is not completed yet: " 1327 + DatabaseUtils.dumpCurrentRowToString(cursor)); 1328 } 1329 filePath = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_LOCAL_FILENAME)); 1330 if (filePath == null) { 1331 throw new IllegalStateException("Download doesn't have a valid file path: " 1332 + DatabaseUtils.dumpCurrentRowToString(cursor)); 1333 } else if (!new File(filePath).exists()) { 1334 throw new IllegalStateException("Downloaded file doesn't exist anymore: " 1335 + DatabaseUtils.dumpCurrentRowToString(cursor)); 1336 } 1337 } else { 1338 throw new IllegalStateException("Missing download id=" + id); 1339 } 1340 } 1341 1342 final File before = new File(filePath); 1343 final File after = new File(before.getParentFile(), displayName); 1344 1345 if (after.exists()) { 1346 throw new IllegalStateException("File already exists: " + after); 1347 } 1348 if (!before.renameTo(after)) { 1349 throw new IllegalStateException( 1350 "Failed to rename file from " + before + " to " + after); 1351 } 1352 1353 // TODO: DownloadProvider.update() should take care of updating corresponding 1354 // MediaProvider entries. 1355 MediaStore.scanFile(mResolver, before); 1356 MediaStore.scanFile(mResolver, after); 1357 1358 final ContentValues values = new ContentValues(); 1359 values.put(Downloads.Impl.COLUMN_TITLE, displayName); 1360 values.put(Downloads.Impl._DATA, after.toString()); 1361 values.putNull(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI); 1362 final long[] ids = { id }; 1363 1364 return mResolver.update( 1365 mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids)) == 1; 1366 } 1367 1368 /** 1369 * Returns recommended maximum size, in bytes, of downloads that may go over a mobile 1370 * connection; or null if there's no recommended limit. The user will have the option to bypass 1371 * this limit. 1372 * 1373 * @param context the {@link Context} to use for accessing the {@link ContentResolver} 1374 * @return recommended maximum size, in bytes, of downloads that may go over a mobile 1375 * connection; or null if there's no recommended limit. 1376 */ getRecommendedMaxBytesOverMobile(Context context)1377 public static Long getRecommendedMaxBytesOverMobile(Context context) { 1378 try { 1379 return Settings.Global.getLong(context.getContentResolver(), 1380 Settings.Global.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE); 1381 } catch (SettingNotFoundException exc) { 1382 return null; 1383 } 1384 } 1385 1386 /** {@hide} */ isActiveNetworkExpensive(Context context)1387 public static boolean isActiveNetworkExpensive(Context context) { 1388 // TODO: connect to NetworkPolicyManager 1389 return false; 1390 } 1391 1392 /** {@hide} */ getActiveNetworkWarningBytes(Context context)1393 public static long getActiveNetworkWarningBytes(Context context) { 1394 // TODO: connect to NetworkPolicyManager 1395 return -1; 1396 } 1397 1398 /** 1399 * Adds a file to the downloads database system, so it could appear in Downloads App 1400 * (and thus become eligible for management by the Downloads App). 1401 * <p> 1402 * It is helpful to make the file scannable by MediaScanner by setting the param 1403 * isMediaScannerScannable to true. It makes the file visible in media managing 1404 * applications such as Gallery App, which could be a useful purpose of using this API. 1405 * 1406 * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above, 1407 * {@code path} must be within directories owned by the application 1408 * {e.g. {@link Context#getExternalFilesDir(String)}} or if the application is running under 1409 * the legacy storage model (see 1410 * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage 1411 * android:requestLegacyExternalStorage}), {@code path} can also be within the top-level 1412 * Downloads directory (as returned by 1413 * {@link Environment#getExternalStoragePublicDirectory(String)} with 1414 * {@link Environment#DIRECTORY_DOWNLOADS}). 1415 * 1416 * @param title the title that would appear for this file in Downloads App. 1417 * @param description the description that would appear for this file in Downloads App. 1418 * @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files 1419 * scanned by MediaScanner appear in the applications used to view media (for example, 1420 * Gallery app). 1421 * @param mimeType mimetype of the file. 1422 * @param path absolute pathname to the file. The file should be world-readable, so that it can 1423 * be managed by the Downloads App and any other app that is used to read it (for example, 1424 * Gallery app to display the file, if the file contents represent a video/image). 1425 * @param length length of the downloaded file 1426 * @param showNotification true if a notification is to be sent, false otherwise 1427 * @return an ID for the download entry added to the downloads app, unique across the system 1428 * This ID is used to make future calls related to this download. 1429 * 1430 * @deprecated Apps should instead contribute files to 1431 * {@link android.provider.MediaStore.Downloads} collection to make them available to user 1432 * as part of Downloads. 1433 */ 1434 @Deprecated addCompletedDownload(String title, String description, boolean isMediaScannerScannable, String mimeType, String path, long length, boolean showNotification)1435 public long addCompletedDownload(String title, String description, 1436 boolean isMediaScannerScannable, String mimeType, String path, long length, 1437 boolean showNotification) { 1438 return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path, 1439 length, showNotification, false, null, null); 1440 } 1441 1442 /** 1443 * Adds a file to the downloads database system, so it could appear in Downloads App 1444 * (and thus become eligible for management by the Downloads App). 1445 * <p> 1446 * It is helpful to make the file scannable by MediaScanner by setting the param 1447 * isMediaScannerScannable to true. It makes the file visible in media managing 1448 * applications such as Gallery App, which could be a useful purpose of using this API. 1449 * 1450 * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above, 1451 * {@code path} must be within directories owned by the application 1452 * {e.g. {@link Context#getExternalFilesDir(String)}} or if the application is running under 1453 * the legacy storage model (see 1454 * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage 1455 * android:requestLegacyExternalStorage}), {@code path} can also be within the top-level 1456 * Downloads directory (as returned by 1457 * {@link Environment#getExternalStoragePublicDirectory(String)} with 1458 * {@link Environment#DIRECTORY_DOWNLOADS}). 1459 * 1460 * @param title the title that would appear for this file in Downloads App. 1461 * @param description the description that would appear for this file in Downloads App. 1462 * @param isMediaScannerScannable true if the file is to be scanned by MediaScanner. Files 1463 * scanned by MediaScanner appear in the applications used to view media (for example, 1464 * Gallery app). 1465 * @param mimeType mimetype of the file. 1466 * @param path absolute pathname to the file. The file should be world-readable, so that it can 1467 * be managed by the Downloads App and any other app that is used to read it (for example, 1468 * Gallery app to display the file, if the file contents represent a video/image). 1469 * @param length length of the downloaded file 1470 * @param showNotification true if a notification is to be sent, false otherwise 1471 * @param uri the original HTTP URI of the download 1472 * @param referer the HTTP Referer for the download 1473 * @return an ID for the download entry added to the downloads app, unique across the system 1474 * This ID is used to make future calls related to this download. 1475 * 1476 * @deprecated Apps should instead contribute files to 1477 * {@link android.provider.MediaStore.Downloads} collection to make them available to user 1478 * as part of Downloads. 1479 */ 1480 @Deprecated addCompletedDownload(String title, String description, boolean isMediaScannerScannable, String mimeType, String path, long length, boolean showNotification, Uri uri, Uri referer)1481 public long addCompletedDownload(String title, String description, 1482 boolean isMediaScannerScannable, String mimeType, String path, long length, 1483 boolean showNotification, Uri uri, Uri referer) { 1484 return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path, 1485 length, showNotification, false, uri, referer); 1486 } 1487 1488 /** 1489 * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above, 1490 * {@code path} must be within directories owned by the application 1491 * {e.g. {@link Context#getExternalFilesDir(String)}} or if the application is running under 1492 * the legacy storage model (see 1493 * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage 1494 * android:requestLegacyExternalStorage}), {@code path} can also be within the top-level 1495 * Downloads directory (as returned by 1496 * {@link Environment#getExternalStoragePublicDirectory(String)} with 1497 * {@link Environment#DIRECTORY_DOWNLOADS}). 1498 * 1499 * @deprecated Apps should instead contribute files to 1500 * {@link android.provider.MediaStore.Downloads} collection to make them available to user 1501 * as part of Downloads. 1502 * 1503 * {@hide} 1504 */ 1505 @Deprecated addCompletedDownload(String title, String description, boolean isMediaScannerScannable, String mimeType, String path, long length, boolean showNotification, boolean allowWrite)1506 public long addCompletedDownload(String title, String description, 1507 boolean isMediaScannerScannable, String mimeType, String path, long length, 1508 boolean showNotification, boolean allowWrite) { 1509 return addCompletedDownload(title, description, isMediaScannerScannable, mimeType, path, 1510 length, showNotification, allowWrite, null, null); 1511 } 1512 1513 /** 1514 * <p> For applications targeting {@link android.os.Build.VERSION_CODES#Q} or above, 1515 * {@code path} must be within directories owned by the application 1516 * {e.g. {@link Context#getExternalFilesDir(String)}} or if the application is running under 1517 * the legacy storage model (see 1518 * {@link android.R.styleable#AndroidManifestApplication_requestLegacyExternalStorage 1519 * android:requestLegacyExternalStorage}), {@code path} can also be within the top-level 1520 * Downloads directory (as returned by 1521 * {@link Environment#getExternalStoragePublicDirectory(String)} with 1522 * {@link Environment#DIRECTORY_DOWNLOADS}). 1523 * 1524 * {@hide} 1525 * 1526 * @deprecated Apps should instead contribute files to 1527 * {@link android.provider.MediaStore.Downloads} collection to make them available to user 1528 * as part of Downloads. 1529 */ 1530 @Deprecated addCompletedDownload(String title, String description, boolean isMediaScannerScannable, String mimeType, String path, long length, boolean showNotification, boolean allowWrite, Uri uri, Uri referer)1531 public long addCompletedDownload(String title, String description, 1532 boolean isMediaScannerScannable, String mimeType, String path, long length, 1533 boolean showNotification, boolean allowWrite, Uri uri, Uri referer) { 1534 // make sure the input args are non-null/non-zero 1535 validateArgumentIsNonEmpty("title", title); 1536 validateArgumentIsNonEmpty("description", description); 1537 validateArgumentIsNonEmpty("path", path); 1538 validateArgumentIsNonEmpty("mimeType", mimeType); 1539 if (length < 0) { 1540 throw new IllegalArgumentException(" invalid value for param: totalBytes"); 1541 } 1542 1543 // if there is already an entry with the given path name in downloads.db, return its id 1544 Request request; 1545 if (uri != null) { 1546 request = new Request(uri); 1547 } else { 1548 request = new Request(NON_DOWNLOADMANAGER_DOWNLOAD); 1549 } 1550 request.setTitle(title) 1551 .setDescription(description) 1552 .setMimeType(mimeType); 1553 if (referer != null) { 1554 request.addRequestHeader("Referer", referer.toString()); 1555 } 1556 ContentValues values = request.toContentValues(null); 1557 values.put(Downloads.Impl.COLUMN_DESTINATION, 1558 Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD); 1559 values.put(Downloads.Impl._DATA, path); 1560 values.put(Downloads.Impl.COLUMN_MIME_TYPE, resolveMimeType(new File(path))); 1561 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_SUCCESS); 1562 values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, length); 1563 values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED, 1564 (isMediaScannerScannable) ? Request.SCANNABLE_VALUE_YES : 1565 Request.SCANNABLE_VALUE_NO); 1566 values.put(Downloads.Impl.COLUMN_VISIBILITY, (showNotification) ? 1567 Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION : Request.VISIBILITY_HIDDEN); 1568 values.put(Downloads.Impl.COLUMN_ALLOW_WRITE, allowWrite ? 1 : 0); 1569 Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values); 1570 if (downloadUri == null) { 1571 return -1; 1572 } 1573 return Long.parseLong(downloadUri.getLastPathSegment()); 1574 } 1575 1576 /** 1577 * Shamelessly borrowed from 1578 * {@code packages/providers/MediaProvider/src/com/android/providers/media/util/MimeUtils.java} 1579 * 1580 * @hide 1581 */ resolveMimeType(@onNull File file)1582 private static @NonNull String resolveMimeType(@NonNull File file) { 1583 final String extension = extractFileExtension(file.getPath()); 1584 if (extension == null) return ClipDescription.MIMETYPE_UNKNOWN; 1585 1586 final String mimeType = MimeTypeMap.getSingleton() 1587 .getMimeTypeFromExtension(extension.toLowerCase(Locale.ROOT)); 1588 if (mimeType == null) return ClipDescription.MIMETYPE_UNKNOWN; 1589 1590 return mimeType; 1591 } 1592 1593 /** 1594 * Shamelessly borrowed from 1595 * {@code packages/providers/MediaProvider/src/com/android/providers/media/util/FileUtils.java} 1596 * 1597 * @hide 1598 */ extractDisplayName(@ullable String data)1599 private static @Nullable String extractDisplayName(@Nullable String data) { 1600 if (data == null) return null; 1601 if (data.indexOf('/') == -1) { 1602 return data; 1603 } 1604 if (data.endsWith("/")) { 1605 data = data.substring(0, data.length() - 1); 1606 } 1607 return data.substring(data.lastIndexOf('/') + 1); 1608 } 1609 1610 /** 1611 * Shamelessly borrowed from 1612 * {@code packages/providers/MediaProvider/src/com/android/providers/media/util/FileUtils.java} 1613 * 1614 * @hide 1615 */ extractFileExtension(@ullable String data)1616 private static @Nullable String extractFileExtension(@Nullable String data) { 1617 if (data == null) return null; 1618 data = extractDisplayName(data); 1619 1620 final int lastDot = data.lastIndexOf('.'); 1621 if (lastDot == -1) { 1622 return null; 1623 } else { 1624 return data.substring(lastDot + 1); 1625 } 1626 } 1627 1628 private static final String NON_DOWNLOADMANAGER_DOWNLOAD = 1629 "non-dwnldmngr-download-dont-retry2download"; 1630 validateArgumentIsNonEmpty(String paramName, String val)1631 private static void validateArgumentIsNonEmpty(String paramName, String val) { 1632 if (TextUtils.isEmpty(val)) { 1633 throw new IllegalArgumentException(paramName + " can't be null"); 1634 } 1635 } 1636 1637 /** 1638 * Get the DownloadProvider URI for the download with the given ID. 1639 * 1640 * @hide 1641 */ getDownloadUri(long id)1642 public Uri getDownloadUri(long id) { 1643 return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id); 1644 } 1645 1646 /** 1647 * Get a parameterized SQL WHERE clause to select a bunch of IDs. 1648 */ 1649 @UnsupportedAppUsage getWhereClauseForIds(long[] ids)1650 static String getWhereClauseForIds(long[] ids) { 1651 StringBuilder whereClause = new StringBuilder(); 1652 whereClause.append("("); 1653 for (int i = 0; i < ids.length; i++) { 1654 if (i > 0) { 1655 whereClause.append("OR "); 1656 } 1657 whereClause.append(Downloads.Impl._ID); 1658 whereClause.append(" = ? "); 1659 } 1660 whereClause.append(")"); 1661 return whereClause.toString(); 1662 } 1663 1664 /** 1665 * Get the selection args for a clause returned by {@link #getWhereClauseForIds(long[])}. 1666 */ 1667 @UnsupportedAppUsage getWhereArgsForIds(long[] ids)1668 static String[] getWhereArgsForIds(long[] ids) { 1669 String[] whereArgs = new String[ids.length]; 1670 return getWhereArgsForIds(ids, whereArgs); 1671 } 1672 1673 /** 1674 * Get selection args for a clause returned by {@link #getWhereClauseForIds(long[])} 1675 * and write it to the supplied args array. 1676 */ getWhereArgsForIds(long[] ids, String[] args)1677 static String[] getWhereArgsForIds(long[] ids, String[] args) { 1678 assert(args.length >= ids.length); 1679 for (int i = 0; i < ids.length; i++) { 1680 args[i] = Long.toString(ids[i]); 1681 } 1682 return args; 1683 } 1684 1685 1686 /** 1687 * This class wraps a cursor returned by DownloadProvider -- the "underlying cursor" -- and 1688 * presents a different set of columns, those defined in the DownloadManager.COLUMN_* constants. 1689 * Some columns correspond directly to underlying values while others are computed from 1690 * underlying data. 1691 */ 1692 private static class CursorTranslator extends CursorWrapper { 1693 private final Uri mBaseUri; 1694 private final boolean mAccessFilename; 1695 CursorTranslator(Cursor cursor, Uri baseUri, boolean accessFilename)1696 public CursorTranslator(Cursor cursor, Uri baseUri, boolean accessFilename) { 1697 super(cursor); 1698 mBaseUri = baseUri; 1699 mAccessFilename = accessFilename; 1700 } 1701 1702 @Override getInt(int columnIndex)1703 public int getInt(int columnIndex) { 1704 return (int) getLong(columnIndex); 1705 } 1706 1707 @Override getLong(int columnIndex)1708 public long getLong(int columnIndex) { 1709 if (getColumnName(columnIndex).equals(COLUMN_REASON)) { 1710 return getReason(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS))); 1711 } else if (getColumnName(columnIndex).equals(COLUMN_STATUS)) { 1712 return translateStatus(super.getInt(getColumnIndex(Downloads.Impl.COLUMN_STATUS))); 1713 } else { 1714 return super.getLong(columnIndex); 1715 } 1716 } 1717 1718 @Override getString(int columnIndex)1719 public String getString(int columnIndex) { 1720 final String columnName = getColumnName(columnIndex); 1721 switch (columnName) { 1722 case COLUMN_LOCAL_URI: 1723 return getLocalUri(); 1724 case COLUMN_LOCAL_FILENAME: 1725 if (!mAccessFilename) { 1726 throw new SecurityException( 1727 "COLUMN_LOCAL_FILENAME is deprecated;" 1728 + " use ContentResolver.openFileDescriptor() instead"); 1729 } 1730 default: 1731 return super.getString(columnIndex); 1732 } 1733 } 1734 getLocalUri()1735 private String getLocalUri() { 1736 long destinationType = getLong(getColumnIndex(Downloads.Impl.COLUMN_DESTINATION)); 1737 if (destinationType == Downloads.Impl.DESTINATION_FILE_URI || 1738 destinationType == Downloads.Impl.DESTINATION_EXTERNAL || 1739 destinationType == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD) { 1740 String localPath = super.getString(getColumnIndex(COLUMN_LOCAL_FILENAME)); 1741 if (localPath == null) { 1742 return null; 1743 } 1744 return Uri.fromFile(new File(localPath)).toString(); 1745 } 1746 1747 // return content URI for cache download 1748 long downloadId = getLong(getColumnIndex(Downloads.Impl._ID)); 1749 return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, downloadId).toString(); 1750 } 1751 getReason(int status)1752 private long getReason(int status) { 1753 switch (translateStatus(status)) { 1754 case STATUS_FAILED: 1755 return getErrorCode(status); 1756 1757 case STATUS_PAUSED: 1758 return getPausedReason(status); 1759 1760 default: 1761 return 0; // arbitrary value when status is not an error 1762 } 1763 } 1764 getPausedReason(int status)1765 private long getPausedReason(int status) { 1766 switch (status) { 1767 case Downloads.Impl.STATUS_WAITING_TO_RETRY: 1768 return PAUSED_WAITING_TO_RETRY; 1769 1770 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK: 1771 return PAUSED_WAITING_FOR_NETWORK; 1772 1773 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI: 1774 return PAUSED_QUEUED_FOR_WIFI; 1775 1776 default: 1777 return PAUSED_UNKNOWN; 1778 } 1779 } 1780 getErrorCode(int status)1781 private long getErrorCode(int status) { 1782 if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS) 1783 || (500 <= status && status < 600)) { 1784 // HTTP status code 1785 return status; 1786 } 1787 1788 switch (status) { 1789 case Downloads.Impl.STATUS_FILE_ERROR: 1790 return ERROR_FILE_ERROR; 1791 1792 case Downloads.Impl.STATUS_UNHANDLED_HTTP_CODE: 1793 case Downloads.Impl.STATUS_UNHANDLED_REDIRECT: 1794 return ERROR_UNHANDLED_HTTP_CODE; 1795 1796 case Downloads.Impl.STATUS_HTTP_DATA_ERROR: 1797 return ERROR_HTTP_DATA_ERROR; 1798 1799 case Downloads.Impl.STATUS_TOO_MANY_REDIRECTS: 1800 return ERROR_TOO_MANY_REDIRECTS; 1801 1802 case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR: 1803 return ERROR_INSUFFICIENT_SPACE; 1804 1805 case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR: 1806 return ERROR_DEVICE_NOT_FOUND; 1807 1808 case Downloads.Impl.STATUS_CANNOT_RESUME: 1809 return ERROR_CANNOT_RESUME; 1810 1811 case Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR: 1812 return ERROR_FILE_ALREADY_EXISTS; 1813 1814 default: 1815 return ERROR_UNKNOWN; 1816 } 1817 } 1818 translateStatus(int status)1819 private int translateStatus(int status) { 1820 switch (status) { 1821 case Downloads.Impl.STATUS_PENDING: 1822 return STATUS_PENDING; 1823 1824 case Downloads.Impl.STATUS_RUNNING: 1825 return STATUS_RUNNING; 1826 1827 case Downloads.Impl.STATUS_PAUSED_BY_APP: 1828 case Downloads.Impl.STATUS_WAITING_TO_RETRY: 1829 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK: 1830 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI: 1831 return STATUS_PAUSED; 1832 1833 case Downloads.Impl.STATUS_SUCCESS: 1834 return STATUS_SUCCESSFUL; 1835 1836 default: 1837 assert Downloads.Impl.isStatusError(status); 1838 return STATUS_FAILED; 1839 } 1840 } 1841 } 1842 } 1843