blob: 01414fa7c03a2a82001d18cb962ef5d8594ed7b9 [file] [log] [blame]
Steve Howarda2709362010-07-02 17:12:48 -07001/*
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
Steve Howardd58429f2010-09-27 16:32:39 -070017package android.app;
Steve Howarda2709362010-07-02 17:12:48 -070018
19import android.content.ContentResolver;
Steve Howardeca77fc2010-09-12 18:49:08 -070020import android.content.ContentUris;
Steve Howarda2709362010-07-02 17:12:48 -070021import android.content.ContentValues;
Steve Howard4f564cd2010-09-22 15:57:25 -070022import android.content.Context;
Steve Howarda2709362010-07-02 17:12:48 -070023import android.database.Cursor;
24import android.database.CursorWrapper;
Steve Howardd58429f2010-09-27 16:32:39 -070025import android.net.ConnectivityManager;
26import android.net.Uri;
Steve Howard4f564cd2010-09-22 15:57:25 -070027import android.os.Environment;
Steve Howarda2709362010-07-02 17:12:48 -070028import android.os.ParcelFileDescriptor;
29import android.provider.Downloads;
Vasu Nori5be894e2010-11-02 21:55:30 -070030import android.util.Log;
Steve Howard4f564cd2010-09-22 15:57:25 -070031import android.util.Pair;
Steve Howarda2709362010-07-02 17:12:48 -070032
Steve Howard4f564cd2010-09-22 15:57:25 -070033import java.io.File;
Steve Howarda2709362010-07-02 17:12:48 -070034import java.io.FileNotFoundException;
35import java.util.ArrayList;
36import java.util.Arrays;
Steve Howarda2709362010-07-02 17:12:48 -070037import java.util.HashSet;
38import java.util.List;
Steve Howarda2709362010-07-02 17:12:48 -070039import java.util.Set;
40
41/**
42 * The download manager is a system service that handles long-running HTTP downloads. Clients may
43 * request that a URI be downloaded to a particular destination file. The download manager will
44 * conduct the download in the background, taking care of HTTP interactions and retrying downloads
45 * after failures or across connectivity changes and system reboots.
46 *
47 * Instances of this class should be obtained through
48 * {@link android.content.Context#getSystemService(String)} by passing
49 * {@link android.content.Context#DOWNLOAD_SERVICE}.
Steve Howard610c4352010-09-30 18:30:04 -070050 *
51 * Apps that request downloads through this API should register a broadcast receiver for
52 * {@link #ACTION_NOTIFICATION_CLICKED} to appropriately handle when the user clicks on a running
53 * download in a notification or from the downloads UI.
Steve Howarda2709362010-07-02 17:12:48 -070054 */
55public class DownloadManager {
Vasu Norie7be6bd2010-10-10 14:58:08 -070056 private static final String TAG = "DownloadManager";
57
Steve Howarda2709362010-07-02 17:12:48 -070058 /**
59 * An identifier for a particular download, unique across the system. Clients use this ID to
60 * make subsequent calls related to the download.
61 */
Vasu Norief7e33b2010-10-20 13:26:02 -070062 public final static String COLUMN_ID = Downloads.Impl._ID;
Steve Howarda2709362010-07-02 17:12:48 -070063
64 /**
Steve Howard8651bd52010-08-03 12:35:32 -070065 * The client-supplied title for this download. This will be displayed in system notifications.
66 * Defaults to the empty string.
Steve Howarda2709362010-07-02 17:12:48 -070067 */
Vasu Norief7e33b2010-10-20 13:26:02 -070068 public final static String COLUMN_TITLE = Downloads.Impl.COLUMN_TITLE;
Steve Howarda2709362010-07-02 17:12:48 -070069
70 /**
71 * The client-supplied description of this download. This will be displayed in system
Steve Howard8651bd52010-08-03 12:35:32 -070072 * notifications. Defaults to the empty string.
Steve Howarda2709362010-07-02 17:12:48 -070073 */
Vasu Norief7e33b2010-10-20 13:26:02 -070074 public final static String COLUMN_DESCRIPTION = Downloads.Impl.COLUMN_DESCRIPTION;
Steve Howarda2709362010-07-02 17:12:48 -070075
76 /**
77 * URI to be downloaded.
78 */
Vasu Norief7e33b2010-10-20 13:26:02 -070079 public final static String COLUMN_URI = Downloads.Impl.COLUMN_URI;
Steve Howarda2709362010-07-02 17:12:48 -070080
81 /**
Steve Howard8651bd52010-08-03 12:35:32 -070082 * Internet Media Type of the downloaded file. If no value is provided upon creation, this will
83 * initially be null and will be filled in based on the server's response once the download has
84 * started.
Steve Howarda2709362010-07-02 17:12:48 -070085 *
86 * @see <a href="http://www.ietf.org/rfc/rfc1590.txt">RFC 1590, defining Media Types</a>
87 */
88 public final static String COLUMN_MEDIA_TYPE = "media_type";
89
90 /**
Steve Howard8651bd52010-08-03 12:35:32 -070091 * Total size of the download in bytes. This will initially be -1 and will be filled in once
92 * the download starts.
Steve Howarda2709362010-07-02 17:12:48 -070093 */
94 public final static String COLUMN_TOTAL_SIZE_BYTES = "total_size";
95
96 /**
97 * Uri where downloaded file will be stored. If a destination is supplied by client, that URI
Steve Howard8651bd52010-08-03 12:35:32 -070098 * will be used here. Otherwise, the value will initially be null and will be filled in with a
99 * generated URI once the download has started.
Steve Howarda2709362010-07-02 17:12:48 -0700100 */
101 public final static String COLUMN_LOCAL_URI = "local_uri";
102
103 /**
Doug Zongkeree04af32010-10-08 13:42:16 -0700104 * The pathname of the file where the download is stored.
105 */
106 public final static String COLUMN_LOCAL_FILENAME = "local_filename";
107
108 /**
Steve Howarda2709362010-07-02 17:12:48 -0700109 * Current status of the download, as one of the STATUS_* constants.
110 */
Vasu Norief7e33b2010-10-20 13:26:02 -0700111 public final static String COLUMN_STATUS = Downloads.Impl.COLUMN_STATUS;
Steve Howarda2709362010-07-02 17:12:48 -0700112
113 /**
Steve Howard3e8c1d32010-09-29 17:03:32 -0700114 * Provides more detail on the status of the download. Its meaning depends on the value of
115 * {@link #COLUMN_STATUS}.
Steve Howarda2709362010-07-02 17:12:48 -0700116 *
Steve Howard3e8c1d32010-09-29 17:03:32 -0700117 * When {@link #COLUMN_STATUS} is {@link #STATUS_FAILED}, this indicates the type of error that
118 * occurred. If an HTTP error occurred, this will hold the HTTP status code as defined in RFC
119 * 2616. Otherwise, it will hold one of the ERROR_* constants.
120 *
121 * When {@link #COLUMN_STATUS} is {@link #STATUS_PAUSED}, this indicates why the download is
122 * paused. It will hold one of the PAUSED_* constants.
123 *
124 * If {@link #COLUMN_STATUS} is neither {@link #STATUS_FAILED} nor {@link #STATUS_PAUSED}, this
125 * column's value is undefined.
Steve Howarda2709362010-07-02 17:12:48 -0700126 *
127 * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1">RFC 2616
128 * status codes</a>
129 */
Steve Howard3e8c1d32010-09-29 17:03:32 -0700130 public final static String COLUMN_REASON = "reason";
Steve Howarda2709362010-07-02 17:12:48 -0700131
132 /**
133 * Number of bytes download so far.
134 */
135 public final static String COLUMN_BYTES_DOWNLOADED_SO_FAR = "bytes_so_far";
136
137 /**
Steve Howardadcb6972010-07-12 17:09:25 -0700138 * Timestamp when the download was last modified, in {@link System#currentTimeMillis
Steve Howarda2709362010-07-02 17:12:48 -0700139 * System.currentTimeMillis()} (wall clock time in UTC).
140 */
Steve Howardadcb6972010-07-12 17:09:25 -0700141 public final static String COLUMN_LAST_MODIFIED_TIMESTAMP = "last_modified_timestamp";
Steve Howarda2709362010-07-02 17:12:48 -0700142
Vasu Nori216fa222010-10-12 23:08:13 -0700143 /**
144 * The URI to the corresponding entry in MediaProvider for this downloaded entry. It is
145 * used to delete the entries from MediaProvider database when it is deleted from the
146 * downloaded list.
147 */
Vasu Norief7e33b2010-10-20 13:26:02 -0700148 public static final String COLUMN_MEDIAPROVIDER_URI = Downloads.Impl.COLUMN_MEDIAPROVIDER_URI;
Steve Howarda2709362010-07-02 17:12:48 -0700149
150 /**
151 * Value of {@link #COLUMN_STATUS} when the download is waiting to start.
152 */
153 public final static int STATUS_PENDING = 1 << 0;
154
155 /**
156 * Value of {@link #COLUMN_STATUS} when the download is currently running.
157 */
158 public final static int STATUS_RUNNING = 1 << 1;
159
160 /**
161 * Value of {@link #COLUMN_STATUS} when the download is waiting to retry or resume.
162 */
163 public final static int STATUS_PAUSED = 1 << 2;
164
165 /**
166 * Value of {@link #COLUMN_STATUS} when the download has successfully completed.
167 */
168 public final static int STATUS_SUCCESSFUL = 1 << 3;
169
170 /**
171 * Value of {@link #COLUMN_STATUS} when the download has failed (and will not be retried).
172 */
173 public final static int STATUS_FAILED = 1 << 4;
174
175
176 /**
177 * Value of COLUMN_ERROR_CODE when the download has completed with an error that doesn't fit
178 * under any other error code.
179 */
180 public final static int ERROR_UNKNOWN = 1000;
181
182 /**
Steve Howard3e8c1d32010-09-29 17:03:32 -0700183 * Value of {@link #COLUMN_REASON} when a storage issue arises which doesn't fit under any
Steve Howarda2709362010-07-02 17:12:48 -0700184 * other error code. Use the more specific {@link #ERROR_INSUFFICIENT_SPACE} and
185 * {@link #ERROR_DEVICE_NOT_FOUND} when appropriate.
186 */
187 public final static int ERROR_FILE_ERROR = 1001;
188
189 /**
Steve Howard3e8c1d32010-09-29 17:03:32 -0700190 * Value of {@link #COLUMN_REASON} when an HTTP code was received that download manager
Steve Howarda2709362010-07-02 17:12:48 -0700191 * can't handle.
192 */
193 public final static int ERROR_UNHANDLED_HTTP_CODE = 1002;
194
195 /**
Steve Howard3e8c1d32010-09-29 17:03:32 -0700196 * Value of {@link #COLUMN_REASON} when an error receiving or processing data occurred at
Steve Howarda2709362010-07-02 17:12:48 -0700197 * the HTTP level.
198 */
199 public final static int ERROR_HTTP_DATA_ERROR = 1004;
200
201 /**
Steve Howard3e8c1d32010-09-29 17:03:32 -0700202 * Value of {@link #COLUMN_REASON} when there were too many redirects.
Steve Howarda2709362010-07-02 17:12:48 -0700203 */
204 public final static int ERROR_TOO_MANY_REDIRECTS = 1005;
205
206 /**
Steve Howard3e8c1d32010-09-29 17:03:32 -0700207 * Value of {@link #COLUMN_REASON} when there was insufficient storage space. Typically,
Steve Howarda2709362010-07-02 17:12:48 -0700208 * this is because the SD card is full.
209 */
210 public final static int ERROR_INSUFFICIENT_SPACE = 1006;
211
212 /**
Steve Howard3e8c1d32010-09-29 17:03:32 -0700213 * Value of {@link #COLUMN_REASON} when no external storage device was found. Typically,
Steve Howarda2709362010-07-02 17:12:48 -0700214 * this is because the SD card is not mounted.
215 */
216 public final static int ERROR_DEVICE_NOT_FOUND = 1007;
217
Steve Howardb8e07a52010-07-21 14:53:21 -0700218 /**
Steve Howard3e8c1d32010-09-29 17:03:32 -0700219 * Value of {@link #COLUMN_REASON} when some possibly transient error occurred but we can't
Steve Howard33bbd122010-08-02 17:51:29 -0700220 * resume the download.
221 */
222 public final static int ERROR_CANNOT_RESUME = 1008;
223
224 /**
Steve Howard3e8c1d32010-09-29 17:03:32 -0700225 * Value of {@link #COLUMN_REASON} when the requested destination file already exists (the
Steve Howarda9e87c92010-09-16 12:02:03 -0700226 * download manager will not overwrite an existing file).
227 */
228 public final static int ERROR_FILE_ALREADY_EXISTS = 1009;
229
230 /**
Steve Howard3e8c1d32010-09-29 17:03:32 -0700231 * Value of {@link #COLUMN_REASON} when the download is paused because some network error
232 * occurred and the download manager is waiting before retrying the request.
233 */
234 public final static int PAUSED_WAITING_TO_RETRY = 1;
235
236 /**
237 * Value of {@link #COLUMN_REASON} when the download is waiting for network connectivity to
238 * proceed.
239 */
240 public final static int PAUSED_WAITING_FOR_NETWORK = 2;
241
242 /**
243 * Value of {@link #COLUMN_REASON} when the download exceeds a size limit for downloads over
244 * the mobile network and the download manager is waiting for a Wi-Fi connection to proceed.
245 */
246 public final static int PAUSED_QUEUED_FOR_WIFI = 3;
247
248 /**
249 * Value of {@link #COLUMN_REASON} when the download is paused for some other reason.
250 */
251 public final static int PAUSED_UNKNOWN = 4;
252
253 /**
Steve Howardb8e07a52010-07-21 14:53:21 -0700254 * Broadcast intent action sent by the download manager when a download completes.
255 */
256 public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE";
257
258 /**
Steve Howard610c4352010-09-30 18:30:04 -0700259 * Broadcast intent action sent by the download manager when the user clicks on a running
260 * download, either from a system notification or from the downloads UI.
Steve Howardb8e07a52010-07-21 14:53:21 -0700261 */
262 public final static String ACTION_NOTIFICATION_CLICKED =
263 "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED";
264
265 /**
Steve Howarde78fc182010-09-24 14:59:36 -0700266 * Intent action to launch an activity to display all downloads.
267 */
268 public final static String ACTION_VIEW_DOWNLOADS = "android.intent.action.VIEW_DOWNLOADS";
269
270 /**
Steve Howardb8e07a52010-07-21 14:53:21 -0700271 * Intent extra included with {@link #ACTION_DOWNLOAD_COMPLETE} intents, indicating the ID (as a
272 * long) of the download that just completed.
273 */
274 public static final String EXTRA_DOWNLOAD_ID = "extra_download_id";
Steve Howarda2709362010-07-02 17:12:48 -0700275
Vasu Nori71b8c232010-10-27 15:22:19 -0700276 /**
277 * When clicks on multiple notifications are received, the following
278 * provides an array of download ids corresponding to the download notification that was
279 * clicked. It can be retrieved by the receiver of this
280 * Intent using {@link android.content.Intent#getLongArrayExtra(String)}.
281 */
282 public static final String EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS = "extra_click_download_ids";
283
Steve Howarda2709362010-07-02 17:12:48 -0700284 // this array must contain all public columns
285 private static final String[] COLUMNS = new String[] {
286 COLUMN_ID,
Vasu Nori216fa222010-10-12 23:08:13 -0700287 COLUMN_MEDIAPROVIDER_URI,
Vasu Nori5be894e2010-11-02 21:55:30 -0700288 Downloads.Impl.COLUMN_DESTINATION,
Steve Howarda2709362010-07-02 17:12:48 -0700289 COLUMN_TITLE,
290 COLUMN_DESCRIPTION,
291 COLUMN_URI,
292 COLUMN_MEDIA_TYPE,
293 COLUMN_TOTAL_SIZE_BYTES,
294 COLUMN_LOCAL_URI,
295 COLUMN_STATUS,
Steve Howard3e8c1d32010-09-29 17:03:32 -0700296 COLUMN_REASON,
Steve Howarda2709362010-07-02 17:12:48 -0700297 COLUMN_BYTES_DOWNLOADED_SO_FAR,
Doug Zongkeree04af32010-10-08 13:42:16 -0700298 COLUMN_LAST_MODIFIED_TIMESTAMP,
Vasu Nori5be894e2010-11-02 21:55:30 -0700299 COLUMN_LOCAL_FILENAME,
Steve Howarda2709362010-07-02 17:12:48 -0700300 };
301
302 // columns to request from DownloadProvider
303 private static final String[] UNDERLYING_COLUMNS = new String[] {
304 Downloads.Impl._ID,
Vasu Nori216fa222010-10-12 23:08:13 -0700305 Downloads.Impl.COLUMN_MEDIAPROVIDER_URI,
Vasu Nori5be894e2010-11-02 21:55:30 -0700306 Downloads.Impl.COLUMN_DESTINATION,
Vasu Norief7e33b2010-10-20 13:26:02 -0700307 Downloads.Impl.COLUMN_TITLE,
308 Downloads.Impl.COLUMN_DESCRIPTION,
309 Downloads.Impl.COLUMN_URI,
310 Downloads.Impl.COLUMN_MIME_TYPE,
311 Downloads.Impl.COLUMN_TOTAL_BYTES,
312 Downloads.Impl.COLUMN_STATUS,
313 Downloads.Impl.COLUMN_CURRENT_BYTES,
314 Downloads.Impl.COLUMN_LAST_MODIFICATION,
Steve Howarda9e87c92010-09-16 12:02:03 -0700315 Downloads.Impl.COLUMN_FILE_NAME_HINT,
Steve Howardbb0d23b2010-09-22 18:56:29 -0700316 Downloads.Impl._DATA,
Steve Howarda2709362010-07-02 17:12:48 -0700317 };
318
319 private static final Set<String> LONG_COLUMNS = new HashSet<String>(
Steve Howard3e8c1d32010-09-29 17:03:32 -0700320 Arrays.asList(COLUMN_ID, COLUMN_TOTAL_SIZE_BYTES, COLUMN_STATUS, COLUMN_REASON,
Vasu Nori5be894e2010-11-02 21:55:30 -0700321 COLUMN_BYTES_DOWNLOADED_SO_FAR, COLUMN_LAST_MODIFIED_TIMESTAMP,
322 Downloads.Impl.COLUMN_DESTINATION));
Steve Howarda2709362010-07-02 17:12:48 -0700323
324 /**
Steve Howard4f564cd2010-09-22 15:57:25 -0700325 * This class contains all the information necessary to request a new download. The URI is the
Steve Howarda2709362010-07-02 17:12:48 -0700326 * only required parameter.
Steve Howard4f564cd2010-09-22 15:57:25 -0700327 *
328 * Note that the default download destination is a shared volume where the system might delete
329 * your file if it needs to reclaim space for system use. If this is a problem, use a location
330 * on external storage (see {@link #setDestinationUri(Uri)}.
Steve Howarda2709362010-07-02 17:12:48 -0700331 */
332 public static class Request {
333 /**
Steve Howardb8e07a52010-07-21 14:53:21 -0700334 * Bit flag for {@link #setAllowedNetworkTypes} corresponding to
335 * {@link ConnectivityManager#TYPE_MOBILE}.
336 */
337 public static final int NETWORK_MOBILE = 1 << 0;
Steve Howarda2709362010-07-02 17:12:48 -0700338
Steve Howardb8e07a52010-07-21 14:53:21 -0700339 /**
340 * Bit flag for {@link #setAllowedNetworkTypes} corresponding to
341 * {@link ConnectivityManager#TYPE_WIFI}.
342 */
343 public static final int NETWORK_WIFI = 1 << 1;
344
Steve Howardb8e07a52010-07-21 14:53:21 -0700345 private Uri mUri;
346 private Uri mDestinationUri;
Steve Howard4f564cd2010-09-22 15:57:25 -0700347 private List<Pair<String, String>> mRequestHeaders = new ArrayList<Pair<String, String>>();
348 private CharSequence mTitle;
349 private CharSequence mDescription;
Steve Howard4f564cd2010-09-22 15:57:25 -0700350 private String mMimeType;
Steve Howardb8e07a52010-07-21 14:53:21 -0700351 private boolean mRoamingAllowed = true;
352 private int mAllowedNetworkTypes = ~0; // default to all network types allowed
Steve Howard90fb15a2010-09-09 16:13:41 -0700353 private boolean mIsVisibleInDownloadsUi = true;
Vasu Nori5be894e2010-11-02 21:55:30 -0700354 private boolean mScannable = false;
Vasu Nori1cde3fb2010-11-05 11:02:52 -0700355 /** if a file is designated as a MediaScanner scannable file, the following value is
356 * stored in the database column {@link Downloads.Impl#COLUMN_MEDIA_SCANNED}.
357 */
358 private static final int SCANNABLE_VALUE_YES = 0;
359 // value of 1 is stored in the above column by DownloadProvider after it is scanned by
360 // MediaScanner
361 /** if a file is designated as a file that should not be scanned by MediaScanner,
362 * the following value is stored in the database column
363 * {@link Downloads.Impl#COLUMN_MEDIA_SCANNED}.
364 */
365 private static final int SCANNABLE_VALUE_NO = 2;
Steve Howarda2709362010-07-02 17:12:48 -0700366
367 /**
Vasu Nori4c6e5df2010-10-26 17:00:16 -0700368 * This download is visible but only shows in the notifications
369 * while it's in progress.
370 */
371 public static final int VISIBILITY_VISIBLE = 0;
372
373 /**
374 * This download is visible and shows in the notifications while
375 * in progress and after completion.
376 */
377 public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1;
378
379 /**
380 * This download doesn't show in the UI or in the notifications.
381 */
382 public static final int VISIBILITY_HIDDEN = 2;
383
384 /** can take any of the following values: {@link #VISIBILITY_HIDDEN}
385 * {@link #VISIBILITY_VISIBLE_NOTIFY_COMPLETED}, {@link #VISIBILITY_VISIBLE}
386 */
387 private int mNotificationVisibility = VISIBILITY_VISIBLE;
388
389 /**
Steve Howarda2709362010-07-02 17:12:48 -0700390 * @param uri the HTTP URI to download.
391 */
392 public Request(Uri uri) {
393 if (uri == null) {
394 throw new NullPointerException();
395 }
396 String scheme = uri.getScheme();
Paul Westbrook86a60192010-09-15 12:55:49 -0700397 if (scheme == null || (!scheme.equals("http") && !scheme.equals("https"))) {
398 throw new IllegalArgumentException("Can only download HTTP/HTTPS URIs: " + uri);
Steve Howarda2709362010-07-02 17:12:48 -0700399 }
400 mUri = uri;
401 }
402
403 /**
Steve Howard4f564cd2010-09-22 15:57:25 -0700404 * Set the local destination for the downloaded file. Must be a file URI to a path on
Steve Howarda2709362010-07-02 17:12:48 -0700405 * external storage, and the calling application must have the WRITE_EXTERNAL_STORAGE
406 * permission.
Vasu Nori5be894e2010-11-02 21:55:30 -0700407 * <p>
408 * The downloaded file is not scanned by MediaScanner.
409 * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}.
410 * <p>
Steve Howard4f564cd2010-09-22 15:57:25 -0700411 * By default, downloads are saved to a generated filename in the shared download cache and
412 * may be deleted by the system at any time to reclaim space.
Steve Howarda2709362010-07-02 17:12:48 -0700413 *
414 * @return this object
415 */
416 public Request setDestinationUri(Uri uri) {
417 mDestinationUri = uri;
418 return this;
419 }
420
421 /**
Steve Howard4f564cd2010-09-22 15:57:25 -0700422 * Set the local destination for the downloaded file to a path within the application's
423 * external files directory (as returned by {@link Context#getExternalFilesDir(String)}.
Vasu Nori5be894e2010-11-02 21:55:30 -0700424 * <p>
425 * The downloaded file is not scanned by MediaScanner.
426 * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}.
Steve Howard4f564cd2010-09-22 15:57:25 -0700427 *
428 * @param context the {@link Context} to use in determining the external files directory
429 * @param dirType the directory type to pass to {@link Context#getExternalFilesDir(String)}
430 * @param subPath the path within the external directory, including the destination filename
431 * @return this object
432 */
433 public Request setDestinationInExternalFilesDir(Context context, String dirType,
434 String subPath) {
435 setDestinationFromBase(context.getExternalFilesDir(dirType), subPath);
436 return this;
437 }
438
439 /**
440 * Set the local destination for the downloaded file to a path within the public external
441 * storage directory (as returned by
442 * {@link Environment#getExternalStoragePublicDirectory(String)}.
Vasu Nori5be894e2010-11-02 21:55:30 -0700443 *<p>
444 * The downloaded file is not scanned by MediaScanner.
445 * But it can be made scannable by calling {@link #allowScanningByMediaScanner()}.
Steve Howard4f564cd2010-09-22 15:57:25 -0700446 *
447 * @param dirType the directory type to pass to
448 * {@link Environment#getExternalStoragePublicDirectory(String)}
449 * @param subPath the path within the external directory, including the destination filename
450 * @return this object
451 */
452 public Request setDestinationInExternalPublicDir(String dirType, String subPath) {
453 setDestinationFromBase(Environment.getExternalStoragePublicDirectory(dirType), subPath);
454 return this;
455 }
456
457 private void setDestinationFromBase(File base, String subPath) {
458 if (subPath == null) {
459 throw new NullPointerException("subPath cannot be null");
460 }
461 mDestinationUri = Uri.withAppendedPath(Uri.fromFile(base), subPath);
462 }
463
464 /**
Vasu Nori5be894e2010-11-02 21:55:30 -0700465 * If the file to be downloaded is to be scanned by MediaScanner, this method
466 * should be called before {@link DownloadManager#enqueue(Request)} is called.
467 */
468 public void allowScanningByMediaScanner() {
469 mScannable = true;
470 }
471
472 /**
Steve Howard4f564cd2010-09-22 15:57:25 -0700473 * Add an HTTP header to be included with the download request. The header will be added to
474 * the end of the list.
Steve Howarda2709362010-07-02 17:12:48 -0700475 * @param header HTTP header name
476 * @param value header value
477 * @return this object
Steve Howard4f564cd2010-09-22 15:57:25 -0700478 * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">HTTP/1.1
479 * Message Headers</a>
Steve Howarda2709362010-07-02 17:12:48 -0700480 */
Steve Howard4f564cd2010-09-22 15:57:25 -0700481 public Request addRequestHeader(String header, String value) {
482 if (header == null) {
483 throw new NullPointerException("header cannot be null");
484 }
485 if (header.contains(":")) {
486 throw new IllegalArgumentException("header may not contain ':'");
487 }
488 if (value == null) {
489 value = "";
490 }
491 mRequestHeaders.add(Pair.create(header, value));
Steve Howarda2709362010-07-02 17:12:48 -0700492 return this;
493 }
494
495 /**
Steve Howard610c4352010-09-30 18:30:04 -0700496 * Set the title of this download, to be displayed in notifications (if enabled). If no
497 * title is given, a default one will be assigned based on the download filename, once the
498 * download starts.
Steve Howarda2709362010-07-02 17:12:48 -0700499 * @return this object
500 */
Steve Howard4f564cd2010-09-22 15:57:25 -0700501 public Request setTitle(CharSequence title) {
Steve Howarda2709362010-07-02 17:12:48 -0700502 mTitle = title;
503 return this;
504 }
505
506 /**
507 * Set a description of this download, to be displayed in notifications (if enabled)
508 * @return this object
509 */
Steve Howard4f564cd2010-09-22 15:57:25 -0700510 public Request setDescription(CharSequence description) {
Steve Howarda2709362010-07-02 17:12:48 -0700511 mDescription = description;
512 return this;
513 }
514
515 /**
Steve Howard4f564cd2010-09-22 15:57:25 -0700516 * Set the MIME content type of this download. This will override the content type declared
Steve Howarda2709362010-07-02 17:12:48 -0700517 * in the server's response.
Steve Howard4f564cd2010-09-22 15:57:25 -0700518 * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7">HTTP/1.1
519 * Media Types</a>
Steve Howarda2709362010-07-02 17:12:48 -0700520 * @return this object
521 */
Steve Howard4f564cd2010-09-22 15:57:25 -0700522 public Request setMimeType(String mimeType) {
523 mMimeType = mimeType;
Steve Howarda2709362010-07-02 17:12:48 -0700524 return this;
525 }
526
527 /**
Steve Howard8e15afe2010-07-28 17:12:40 -0700528 * Control whether a system notification is posted by the download manager while this
529 * download is running. If enabled, the download manager posts notifications about downloads
530 * through the system {@link android.app.NotificationManager}. By default, a notification is
531 * shown.
Steve Howarda2709362010-07-02 17:12:48 -0700532 *
Steve Howard8e15afe2010-07-28 17:12:40 -0700533 * If set to false, this requires the permission
534 * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION.
535 *
536 * @param show whether the download manager should show a notification for this download.
Steve Howarda2709362010-07-02 17:12:48 -0700537 * @return this object
Vasu Nori4c6e5df2010-10-26 17:00:16 -0700538 * @deprecated use {@link #setNotificationVisibility(int)}
Steve Howarda2709362010-07-02 17:12:48 -0700539 */
Vasu Nori4c6e5df2010-10-26 17:00:16 -0700540 @Deprecated
Steve Howard8e15afe2010-07-28 17:12:40 -0700541 public Request setShowRunningNotification(boolean show) {
Vasu Nori4c6e5df2010-10-26 17:00:16 -0700542 return (show) ? setNotificationVisibility(VISIBILITY_VISIBLE) :
543 setNotificationVisibility(VISIBILITY_HIDDEN);
544 }
545
546 /**
547 * Control whether a system notification is posted by the download manager while this
548 * download is running or when it is completed.
549 * If enabled, the download manager posts notifications about downloads
550 * through the system {@link android.app.NotificationManager}.
551 * By default, a notification is shown only when the download is in progress.
552 *<p>
553 * It can take the following values: {@link #VISIBILITY_HIDDEN},
554 * {@link #VISIBILITY_VISIBLE},
555 * {@link #VISIBILITY_VISIBLE_NOTIFY_COMPLETED}.
556 *<p>
557 * If set to {@link #VISIBILITY_HIDDEN}, this requires the permission
558 * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION.
559 *
560 * @param visibility the visibility setting value
561 * @return this object
562 */
563 public Request setNotificationVisibility(int visibility) {
564 mNotificationVisibility = visibility;
Steve Howarda2709362010-07-02 17:12:48 -0700565 return this;
566 }
567
Steve Howardb8e07a52010-07-21 14:53:21 -0700568 /**
569 * Restrict the types of networks over which this download may proceed. By default, all
570 * network types are allowed.
571 * @param flags any combination of the NETWORK_* bit flags.
572 * @return this object
573 */
Steve Howarda2709362010-07-02 17:12:48 -0700574 public Request setAllowedNetworkTypes(int flags) {
Steve Howardb8e07a52010-07-21 14:53:21 -0700575 mAllowedNetworkTypes = flags;
576 return this;
Steve Howarda2709362010-07-02 17:12:48 -0700577 }
578
Steve Howardb8e07a52010-07-21 14:53:21 -0700579 /**
580 * Set whether this download may proceed over a roaming connection. By default, roaming is
581 * allowed.
582 * @param allowed whether to allow a roaming connection to be used
583 * @return this object
584 */
Steve Howarda2709362010-07-02 17:12:48 -0700585 public Request setAllowedOverRoaming(boolean allowed) {
Steve Howardb8e07a52010-07-21 14:53:21 -0700586 mRoamingAllowed = allowed;
587 return this;
Steve Howarda2709362010-07-02 17:12:48 -0700588 }
589
590 /**
Steve Howard90fb15a2010-09-09 16:13:41 -0700591 * Set whether this download should be displayed in the system's Downloads UI. True by
592 * default.
593 * @param isVisible whether to display this download in the Downloads UI
594 * @return this object
595 */
596 public Request setVisibleInDownloadsUi(boolean isVisible) {
597 mIsVisibleInDownloadsUi = isVisible;
598 return this;
599 }
600
601 /**
Steve Howarda2709362010-07-02 17:12:48 -0700602 * @return ContentValues to be passed to DownloadProvider.insert()
603 */
Steve Howardb8e07a52010-07-21 14:53:21 -0700604 ContentValues toContentValues(String packageName) {
Steve Howarda2709362010-07-02 17:12:48 -0700605 ContentValues values = new ContentValues();
606 assert mUri != null;
Vasu Norief7e33b2010-10-20 13:26:02 -0700607 values.put(Downloads.Impl.COLUMN_URI, mUri.toString());
Steve Howardb8e07a52010-07-21 14:53:21 -0700608 values.put(Downloads.Impl.COLUMN_IS_PUBLIC_API, true);
Vasu Norief7e33b2010-10-20 13:26:02 -0700609 values.put(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, packageName);
Steve Howarda2709362010-07-02 17:12:48 -0700610
611 if (mDestinationUri != null) {
Vasu Norief7e33b2010-10-20 13:26:02 -0700612 values.put(Downloads.Impl.COLUMN_DESTINATION, Downloads.Impl.DESTINATION_FILE_URI);
613 values.put(Downloads.Impl.COLUMN_FILE_NAME_HINT, mDestinationUri.toString());
Steve Howarda2709362010-07-02 17:12:48 -0700614 } else {
Vasu Norief7e33b2010-10-20 13:26:02 -0700615 values.put(Downloads.Impl.COLUMN_DESTINATION,
616 Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE);
Steve Howarda2709362010-07-02 17:12:48 -0700617 }
Vasu Nori5be894e2010-11-02 21:55:30 -0700618 // is the file supposed to be media-scannable?
Vasu Nori1cde3fb2010-11-05 11:02:52 -0700619 values.put(Downloads.Impl.COLUMN_MEDIA_SCANNED, (mScannable) ? SCANNABLE_VALUE_YES :
620 SCANNABLE_VALUE_NO);
Steve Howarda2709362010-07-02 17:12:48 -0700621
622 if (!mRequestHeaders.isEmpty()) {
Steve Howardea9147d2010-07-13 19:02:45 -0700623 encodeHttpHeaders(values);
Steve Howarda2709362010-07-02 17:12:48 -0700624 }
625
Vasu Norief7e33b2010-10-20 13:26:02 -0700626 putIfNonNull(values, Downloads.Impl.COLUMN_TITLE, mTitle);
627 putIfNonNull(values, Downloads.Impl.COLUMN_DESCRIPTION, mDescription);
628 putIfNonNull(values, Downloads.Impl.COLUMN_MIME_TYPE, mMimeType);
Steve Howarda2709362010-07-02 17:12:48 -0700629
Vasu Nori4c6e5df2010-10-26 17:00:16 -0700630 values.put(Downloads.Impl.COLUMN_VISIBILITY, mNotificationVisibility);
Steve Howardb8e07a52010-07-21 14:53:21 -0700631 values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes);
632 values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed);
Steve Howard90fb15a2010-09-09 16:13:41 -0700633 values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, mIsVisibleInDownloadsUi);
Steve Howardb8e07a52010-07-21 14:53:21 -0700634
Steve Howarda2709362010-07-02 17:12:48 -0700635 return values;
636 }
637
Steve Howardea9147d2010-07-13 19:02:45 -0700638 private void encodeHttpHeaders(ContentValues values) {
639 int index = 0;
Steve Howard4f564cd2010-09-22 15:57:25 -0700640 for (Pair<String, String> header : mRequestHeaders) {
641 String headerString = header.first + ": " + header.second;
Steve Howardea9147d2010-07-13 19:02:45 -0700642 values.put(Downloads.Impl.RequestHeaders.INSERT_KEY_PREFIX + index, headerString);
643 index++;
644 }
645 }
646
Steve Howard4f564cd2010-09-22 15:57:25 -0700647 private void putIfNonNull(ContentValues contentValues, String key, Object value) {
Steve Howarda2709362010-07-02 17:12:48 -0700648 if (value != null) {
Steve Howard4f564cd2010-09-22 15:57:25 -0700649 contentValues.put(key, value.toString());
Steve Howarda2709362010-07-02 17:12:48 -0700650 }
651 }
652 }
653
654 /**
655 * This class may be used to filter download manager queries.
656 */
657 public static class Query {
Steve Howardf054e192010-09-01 18:26:26 -0700658 /**
659 * Constant for use with {@link #orderBy}
660 * @hide
661 */
662 public static final int ORDER_ASCENDING = 1;
663
664 /**
665 * Constant for use with {@link #orderBy}
666 * @hide
667 */
668 public static final int ORDER_DESCENDING = 2;
669
Steve Howard64c48b82010-10-07 17:53:52 -0700670 private long[] mIds = null;
Steve Howarda2709362010-07-02 17:12:48 -0700671 private Integer mStatusFlags = null;
Vasu Norief7e33b2010-10-20 13:26:02 -0700672 private String mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION;
Steve Howardf054e192010-09-01 18:26:26 -0700673 private int mOrderDirection = ORDER_DESCENDING;
Steve Howard90fb15a2010-09-09 16:13:41 -0700674 private boolean mOnlyIncludeVisibleInDownloadsUi = false;
Steve Howarda2709362010-07-02 17:12:48 -0700675
676 /**
Steve Howard64c48b82010-10-07 17:53:52 -0700677 * Include only the downloads with the given IDs.
Steve Howarda2709362010-07-02 17:12:48 -0700678 * @return this object
679 */
Steve Howard64c48b82010-10-07 17:53:52 -0700680 public Query setFilterById(long... ids) {
681 mIds = ids;
Steve Howarda2709362010-07-02 17:12:48 -0700682 return this;
683 }
684
685 /**
686 * Include only downloads with status matching any the given status flags.
687 * @param flags any combination of the STATUS_* bit flags
688 * @return this object
689 */
690 public Query setFilterByStatus(int flags) {
691 mStatusFlags = flags;
692 return this;
693 }
694
695 /**
Steve Howard90fb15a2010-09-09 16:13:41 -0700696 * Controls whether this query includes downloads not visible in the system's Downloads UI.
697 * @param value if true, this query will only include downloads that should be displayed in
698 * the system's Downloads UI; if false (the default), this query will include
699 * both visible and invisible downloads.
700 * @return this object
701 * @hide
702 */
703 public Query setOnlyIncludeVisibleInDownloadsUi(boolean value) {
704 mOnlyIncludeVisibleInDownloadsUi = value;
705 return this;
706 }
707
708 /**
Steve Howardf054e192010-09-01 18:26:26 -0700709 * Change the sort order of the returned Cursor.
710 *
711 * @param column one of the COLUMN_* constants; currently, only
712 * {@link #COLUMN_LAST_MODIFIED_TIMESTAMP} and {@link #COLUMN_TOTAL_SIZE_BYTES} are
713 * supported.
714 * @param direction either {@link #ORDER_ASCENDING} or {@link #ORDER_DESCENDING}
715 * @return this object
716 * @hide
717 */
718 public Query orderBy(String column, int direction) {
719 if (direction != ORDER_ASCENDING && direction != ORDER_DESCENDING) {
720 throw new IllegalArgumentException("Invalid direction: " + direction);
721 }
722
723 if (column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP)) {
Vasu Norief7e33b2010-10-20 13:26:02 -0700724 mOrderByColumn = Downloads.Impl.COLUMN_LAST_MODIFICATION;
Steve Howardf054e192010-09-01 18:26:26 -0700725 } else if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) {
Vasu Norief7e33b2010-10-20 13:26:02 -0700726 mOrderByColumn = Downloads.Impl.COLUMN_TOTAL_BYTES;
Steve Howardf054e192010-09-01 18:26:26 -0700727 } else {
728 throw new IllegalArgumentException("Cannot order by " + column);
729 }
730 mOrderDirection = direction;
731 return this;
732 }
733
734 /**
Steve Howarda2709362010-07-02 17:12:48 -0700735 * Run this query using the given ContentResolver.
736 * @param projection the projection to pass to ContentResolver.query()
737 * @return the Cursor returned by ContentResolver.query()
738 */
Steve Howardeca77fc2010-09-12 18:49:08 -0700739 Cursor runQuery(ContentResolver resolver, String[] projection, Uri baseUri) {
740 Uri uri = baseUri;
Steve Howard90fb15a2010-09-09 16:13:41 -0700741 List<String> selectionParts = new ArrayList<String>();
Steve Howard64c48b82010-10-07 17:53:52 -0700742 String[] selectionArgs = null;
Steve Howarda2709362010-07-02 17:12:48 -0700743
Steve Howard64c48b82010-10-07 17:53:52 -0700744 if (mIds != null) {
745 selectionParts.add(getWhereClauseForIds(mIds));
746 selectionArgs = getWhereArgsForIds(mIds);
Steve Howarda2709362010-07-02 17:12:48 -0700747 }
748
749 if (mStatusFlags != null) {
750 List<String> parts = new ArrayList<String>();
751 if ((mStatusFlags & STATUS_PENDING) != 0) {
Vasu Norief7e33b2010-10-20 13:26:02 -0700752 parts.add(statusClause("=", Downloads.Impl.STATUS_PENDING));
Steve Howarda2709362010-07-02 17:12:48 -0700753 }
754 if ((mStatusFlags & STATUS_RUNNING) != 0) {
Vasu Norief7e33b2010-10-20 13:26:02 -0700755 parts.add(statusClause("=", Downloads.Impl.STATUS_RUNNING));
Steve Howarda2709362010-07-02 17:12:48 -0700756 }
757 if ((mStatusFlags & STATUS_PAUSED) != 0) {
Steve Howard3e8c1d32010-09-29 17:03:32 -0700758 parts.add(statusClause("=", Downloads.Impl.STATUS_PAUSED_BY_APP));
759 parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_TO_RETRY));
760 parts.add(statusClause("=", Downloads.Impl.STATUS_WAITING_FOR_NETWORK));
761 parts.add(statusClause("=", Downloads.Impl.STATUS_QUEUED_FOR_WIFI));
Steve Howarda2709362010-07-02 17:12:48 -0700762 }
763 if ((mStatusFlags & STATUS_SUCCESSFUL) != 0) {
Vasu Norief7e33b2010-10-20 13:26:02 -0700764 parts.add(statusClause("=", Downloads.Impl.STATUS_SUCCESS));
Steve Howarda2709362010-07-02 17:12:48 -0700765 }
766 if ((mStatusFlags & STATUS_FAILED) != 0) {
767 parts.add("(" + statusClause(">=", 400)
768 + " AND " + statusClause("<", 600) + ")");
769 }
Steve Howard90fb15a2010-09-09 16:13:41 -0700770 selectionParts.add(joinStrings(" OR ", parts));
Steve Howarda2709362010-07-02 17:12:48 -0700771 }
Steve Howardf054e192010-09-01 18:26:26 -0700772
Steve Howard90fb15a2010-09-09 16:13:41 -0700773 if (mOnlyIncludeVisibleInDownloadsUi) {
774 selectionParts.add(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI + " != '0'");
775 }
776
Vasu Nori216fa222010-10-12 23:08:13 -0700777 // only return rows which are not marked 'deleted = 1'
778 selectionParts.add(Downloads.Impl.COLUMN_DELETED + " != '1'");
779
Steve Howard90fb15a2010-09-09 16:13:41 -0700780 String selection = joinStrings(" AND ", selectionParts);
Steve Howardf054e192010-09-01 18:26:26 -0700781 String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC");
782 String orderBy = mOrderByColumn + " " + orderDirection;
783
Steve Howard64c48b82010-10-07 17:53:52 -0700784 return resolver.query(uri, projection, selection, selectionArgs, orderBy);
Steve Howarda2709362010-07-02 17:12:48 -0700785 }
786
787 private String joinStrings(String joiner, Iterable<String> parts) {
788 StringBuilder builder = new StringBuilder();
789 boolean first = true;
790 for (String part : parts) {
791 if (!first) {
792 builder.append(joiner);
793 }
794 builder.append(part);
795 first = false;
796 }
797 return builder.toString();
798 }
799
800 private String statusClause(String operator, int value) {
Vasu Norief7e33b2010-10-20 13:26:02 -0700801 return Downloads.Impl.COLUMN_STATUS + operator + "'" + value + "'";
Steve Howarda2709362010-07-02 17:12:48 -0700802 }
803 }
804
805 private ContentResolver mResolver;
Steve Howardb8e07a52010-07-21 14:53:21 -0700806 private String mPackageName;
Steve Howardeca77fc2010-09-12 18:49:08 -0700807 private Uri mBaseUri = Downloads.Impl.CONTENT_URI;
Steve Howarda2709362010-07-02 17:12:48 -0700808
809 /**
810 * @hide
811 */
Steve Howardb8e07a52010-07-21 14:53:21 -0700812 public DownloadManager(ContentResolver resolver, String packageName) {
Steve Howarda2709362010-07-02 17:12:48 -0700813 mResolver = resolver;
Steve Howardb8e07a52010-07-21 14:53:21 -0700814 mPackageName = packageName;
Steve Howarda2709362010-07-02 17:12:48 -0700815 }
816
817 /**
Steve Howardeca77fc2010-09-12 18:49:08 -0700818 * Makes this object access the download provider through /all_downloads URIs rather than
819 * /my_downloads URIs, for clients that have permission to do so.
820 * @hide
821 */
822 public void setAccessAllDownloads(boolean accessAllDownloads) {
823 if (accessAllDownloads) {
824 mBaseUri = Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI;
825 } else {
826 mBaseUri = Downloads.Impl.CONTENT_URI;
827 }
828 }
829
830 /**
Steve Howarda2709362010-07-02 17:12:48 -0700831 * Enqueue a new download. The download will start automatically once the download manager is
832 * ready to execute it and connectivity is available.
833 *
834 * @param request the parameters specifying this download
835 * @return an ID for the download, unique across the system. This ID is used to make future
836 * calls related to this download.
837 */
838 public long enqueue(Request request) {
Steve Howardb8e07a52010-07-21 14:53:21 -0700839 ContentValues values = request.toContentValues(mPackageName);
Vasu Norief7e33b2010-10-20 13:26:02 -0700840 Uri downloadUri = mResolver.insert(Downloads.Impl.CONTENT_URI, values);
Steve Howarda2709362010-07-02 17:12:48 -0700841 long id = Long.parseLong(downloadUri.getLastPathSegment());
842 return id;
843 }
844
845 /**
Vasu Nori216fa222010-10-12 23:08:13 -0700846 * Marks the specified download as 'to be deleted'. This is done when a completed download
847 * is to be removed but the row was stored without enough info to delete the corresponding
848 * metadata from Mediaprovider database. Actual cleanup of this row is done in DownloadService.
849 *
850 * @param ids the IDs of the downloads to be marked 'deleted'
851 * @return the number of downloads actually updated
852 * @hide
853 */
854 public int markRowDeleted(long... ids) {
855 if (ids == null || ids.length == 0) {
856 // called with nothing to remove!
857 throw new IllegalArgumentException("input param 'ids' can't be null");
858 }
859 ContentValues values = new ContentValues();
860 values.put(Downloads.Impl.COLUMN_DELETED, 1);
861 return mResolver.update(mBaseUri, values, getWhereClauseForIds(ids),
862 getWhereArgsForIds(ids));
863 }
864
865 /**
Steve Howard64c48b82010-10-07 17:53:52 -0700866 * Cancel downloads and remove them from the download manager. Each download will be stopped if
Steve Howarda2709362010-07-02 17:12:48 -0700867 * it was running, and it will no longer be accessible through the download manager. If a file
Steve Howard64c48b82010-10-07 17:53:52 -0700868 * was already downloaded to external storage, it will not be deleted.
Steve Howarda2709362010-07-02 17:12:48 -0700869 *
Steve Howard64c48b82010-10-07 17:53:52 -0700870 * @param ids the IDs of the downloads to remove
871 * @return the number of downloads actually removed
Steve Howarda2709362010-07-02 17:12:48 -0700872 */
Steve Howard64c48b82010-10-07 17:53:52 -0700873 public int remove(long... ids) {
Vasu Norie7be6bd2010-10-10 14:58:08 -0700874 if (ids == null || ids.length == 0) {
875 // called with nothing to remove!
876 throw new IllegalArgumentException("input param 'ids' can't be null");
Steve Howarda2709362010-07-02 17:12:48 -0700877 }
Vasu Norie7be6bd2010-10-10 14:58:08 -0700878 return mResolver.delete(mBaseUri, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
Steve Howarda2709362010-07-02 17:12:48 -0700879 }
880
881 /**
882 * Query the download manager about downloads that have been requested.
883 * @param query parameters specifying filters for this query
884 * @return a Cursor over the result set of downloads, with columns consisting of all the
885 * COLUMN_* constants.
886 */
887 public Cursor query(Query query) {
Steve Howardeca77fc2010-09-12 18:49:08 -0700888 Cursor underlyingCursor = query.runQuery(mResolver, UNDERLYING_COLUMNS, mBaseUri);
Steve Howardf054e192010-09-01 18:26:26 -0700889 if (underlyingCursor == null) {
890 return null;
891 }
Steve Howardeca77fc2010-09-12 18:49:08 -0700892 return new CursorTranslator(underlyingCursor, mBaseUri);
Steve Howarda2709362010-07-02 17:12:48 -0700893 }
894
895 /**
896 * Open a downloaded file for reading. The download must have completed.
897 * @param id the ID of the download
898 * @return a read-only {@link ParcelFileDescriptor}
899 * @throws FileNotFoundException if the destination file does not already exist
900 */
901 public ParcelFileDescriptor openDownloadedFile(long id) throws FileNotFoundException {
902 return mResolver.openFileDescriptor(getDownloadUri(id), "r");
903 }
904
905 /**
Vasu Nori5be894e2010-11-02 21:55:30 -0700906 * Returns {@link Uri} for the given downloaded file id, if the file is
907 * downloaded successfully. otherwise, null is returned.
908 *<p>
909 * If the specified downloaded file is in external storage (for example, /sdcard dir),
910 * then it is assumed to be safe for anyone to read and the returned {@link Uri} can be used
911 * by any app to access the downloaded file.
912 *
913 * @param id the id of the downloaded file.
Vasu Nori1cde3fb2010-11-05 11:02:52 -0700914 * @return the {@link Uri} for the given downloaded file id, if download was successful. null
Vasu Nori5be894e2010-11-02 21:55:30 -0700915 * otherwise.
916 */
917 public Uri getUriForDownloadedFile(long id) {
918 // to check if the file is in cache, get its destination from the database
919 Query query = new Query().setFilterById(id);
920 Cursor cursor = null;
921 try {
922 cursor = query(query);
923 if (cursor == null) {
924 return null;
925 }
926 while (cursor.moveToFirst()) {
927 int status = cursor.getInt(cursor.getColumnIndexOrThrow(
928 DownloadManager.COLUMN_STATUS));
929 if (DownloadManager.STATUS_SUCCESSFUL == status) {
930 int indx = cursor.getColumnIndexOrThrow(
931 Downloads.Impl.COLUMN_DESTINATION);
932 int destination = cursor.getInt(indx);
933 // TODO: if we ever add API to DownloadManager to let the caller specify
Vasu Nori1cde3fb2010-11-05 11:02:52 -0700934 // non-external storage for a downloaded file, then the following code
Vasu Nori5be894e2010-11-02 21:55:30 -0700935 // should also check for that destination.
936 if (destination == Downloads.Impl.DESTINATION_CACHE_PARTITION ||
937 destination == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING ||
938 destination == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE) {
939 // return private uri
940 return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, id);
941 } else {
942 // return public uri
943 return ContentUris.withAppendedId(
944 Downloads.Impl.PUBLICLY_ACCESSIBLE_DOWNLOADS_URI, id);
945 }
946 }
947 }
948 } finally {
949 if (cursor != null) {
950 cursor.close();
951 }
952 }
953 // downloaded file not found or its status is not 'successfully completed'
954 return null;
955 }
956
957 /**
Steve Howard64c48b82010-10-07 17:53:52 -0700958 * Restart the given downloads, which must have already completed (successfully or not). This
Steve Howard90fb15a2010-09-09 16:13:41 -0700959 * method will only work when called from within the download manager's process.
Steve Howard64c48b82010-10-07 17:53:52 -0700960 * @param ids the IDs of the downloads
Steve Howard90fb15a2010-09-09 16:13:41 -0700961 * @hide
962 */
Steve Howard64c48b82010-10-07 17:53:52 -0700963 public void restartDownload(long... ids) {
964 Cursor cursor = query(new Query().setFilterById(ids));
Steve Howard90fb15a2010-09-09 16:13:41 -0700965 try {
Steve Howard64c48b82010-10-07 17:53:52 -0700966 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
967 int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS));
968 if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) {
969 throw new IllegalArgumentException("Cannot restart incomplete download: "
970 + cursor.getLong(cursor.getColumnIndex(COLUMN_ID)));
971 }
Steve Howard90fb15a2010-09-09 16:13:41 -0700972 }
973 } finally {
974 cursor.close();
975 }
976
977 ContentValues values = new ContentValues();
978 values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0);
979 values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1);
980 values.putNull(Downloads.Impl._DATA);
981 values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
Steve Howard64c48b82010-10-07 17:53:52 -0700982 mResolver.update(mBaseUri, values, getWhereClauseForIds(ids), getWhereArgsForIds(ids));
Steve Howard90fb15a2010-09-09 16:13:41 -0700983 }
984
985 /**
Steve Howarda2709362010-07-02 17:12:48 -0700986 * Get the DownloadProvider URI for the download with the given ID.
987 */
Steve Howardeca77fc2010-09-12 18:49:08 -0700988 Uri getDownloadUri(long id) {
989 return ContentUris.withAppendedId(mBaseUri, id);
Steve Howarda2709362010-07-02 17:12:48 -0700990 }
991
992 /**
Steve Howard64c48b82010-10-07 17:53:52 -0700993 * Get a parameterized SQL WHERE clause to select a bunch of IDs.
994 */
995 static String getWhereClauseForIds(long[] ids) {
996 StringBuilder whereClause = new StringBuilder();
Vasu Norie7be6bd2010-10-10 14:58:08 -0700997 whereClause.append("(");
Steve Howard64c48b82010-10-07 17:53:52 -0700998 for (int i = 0; i < ids.length; i++) {
999 if (i > 0) {
Vasu Norie7be6bd2010-10-10 14:58:08 -07001000 whereClause.append("OR ");
Steve Howard64c48b82010-10-07 17:53:52 -07001001 }
Vasu Norie7be6bd2010-10-10 14:58:08 -07001002 whereClause.append(Downloads.Impl._ID);
1003 whereClause.append(" = ? ");
Steve Howard64c48b82010-10-07 17:53:52 -07001004 }
1005 whereClause.append(")");
1006 return whereClause.toString();
1007 }
1008
1009 /**
1010 * Get the selection args for a clause returned by {@link #getWhereClauseForIds(long[])}.
1011 */
1012 static String[] getWhereArgsForIds(long[] ids) {
1013 String[] whereArgs = new String[ids.length];
1014 for (int i = 0; i < ids.length; i++) {
1015 whereArgs[i] = Long.toString(ids[i]);
1016 }
1017 return whereArgs;
1018 }
1019
1020 /**
Steve Howarda2709362010-07-02 17:12:48 -07001021 * This class wraps a cursor returned by DownloadProvider -- the "underlying cursor" -- and
1022 * presents a different set of columns, those defined in the DownloadManager.COLUMN_* constants.
1023 * Some columns correspond directly to underlying values while others are computed from
1024 * underlying data.
1025 */
1026 private static class CursorTranslator extends CursorWrapper {
Steve Howardeca77fc2010-09-12 18:49:08 -07001027 private Uri mBaseUri;
1028
1029 public CursorTranslator(Cursor cursor, Uri baseUri) {
Steve Howarda2709362010-07-02 17:12:48 -07001030 super(cursor);
Steve Howardeca77fc2010-09-12 18:49:08 -07001031 mBaseUri = baseUri;
Steve Howarda2709362010-07-02 17:12:48 -07001032 }
1033
1034 @Override
1035 public int getColumnIndex(String columnName) {
1036 return Arrays.asList(COLUMNS).indexOf(columnName);
1037 }
1038
1039 @Override
1040 public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException {
1041 int index = getColumnIndex(columnName);
1042 if (index == -1) {
Steve Howardf054e192010-09-01 18:26:26 -07001043 throw new IllegalArgumentException("No such column: " + columnName);
Steve Howarda2709362010-07-02 17:12:48 -07001044 }
1045 return index;
1046 }
1047
1048 @Override
1049 public String getColumnName(int columnIndex) {
1050 int numColumns = COLUMNS.length;
1051 if (columnIndex < 0 || columnIndex >= numColumns) {
1052 throw new IllegalArgumentException("Invalid column index " + columnIndex + ", "
1053 + numColumns + " columns exist");
1054 }
1055 return COLUMNS[columnIndex];
1056 }
1057
1058 @Override
1059 public String[] getColumnNames() {
1060 String[] returnColumns = new String[COLUMNS.length];
1061 System.arraycopy(COLUMNS, 0, returnColumns, 0, COLUMNS.length);
1062 return returnColumns;
1063 }
1064
1065 @Override
1066 public int getColumnCount() {
1067 return COLUMNS.length;
1068 }
1069
1070 @Override
1071 public byte[] getBlob(int columnIndex) {
1072 throw new UnsupportedOperationException();
1073 }
1074
1075 @Override
1076 public double getDouble(int columnIndex) {
1077 return getLong(columnIndex);
1078 }
1079
1080 private boolean isLongColumn(String column) {
1081 return LONG_COLUMNS.contains(column);
1082 }
1083
1084 @Override
1085 public float getFloat(int columnIndex) {
1086 return (float) getDouble(columnIndex);
1087 }
1088
1089 @Override
1090 public int getInt(int columnIndex) {
1091 return (int) getLong(columnIndex);
1092 }
1093
1094 @Override
1095 public long getLong(int columnIndex) {
1096 return translateLong(getColumnName(columnIndex));
1097 }
1098
1099 @Override
1100 public short getShort(int columnIndex) {
1101 return (short) getLong(columnIndex);
1102 }
1103
1104 @Override
1105 public String getString(int columnIndex) {
1106 return translateString(getColumnName(columnIndex));
1107 }
1108
1109 private String translateString(String column) {
1110 if (isLongColumn(column)) {
1111 return Long.toString(translateLong(column));
1112 }
1113 if (column.equals(COLUMN_TITLE)) {
Vasu Norief7e33b2010-10-20 13:26:02 -07001114 return getUnderlyingString(Downloads.Impl.COLUMN_TITLE);
Steve Howarda2709362010-07-02 17:12:48 -07001115 }
1116 if (column.equals(COLUMN_DESCRIPTION)) {
Vasu Norief7e33b2010-10-20 13:26:02 -07001117 return getUnderlyingString(Downloads.Impl.COLUMN_DESCRIPTION);
Steve Howarda2709362010-07-02 17:12:48 -07001118 }
1119 if (column.equals(COLUMN_URI)) {
Vasu Norief7e33b2010-10-20 13:26:02 -07001120 return getUnderlyingString(Downloads.Impl.COLUMN_URI);
Steve Howarda2709362010-07-02 17:12:48 -07001121 }
1122 if (column.equals(COLUMN_MEDIA_TYPE)) {
Vasu Norief7e33b2010-10-20 13:26:02 -07001123 return getUnderlyingString(Downloads.Impl.COLUMN_MIME_TYPE);
Steve Howarda2709362010-07-02 17:12:48 -07001124 }
Doug Zongkeree04af32010-10-08 13:42:16 -07001125 if (column.equals(COLUMN_LOCAL_FILENAME)) {
1126 return getUnderlyingString(Downloads.Impl._DATA);
1127 }
Vasu Nori216fa222010-10-12 23:08:13 -07001128 if (column.equals(COLUMN_MEDIAPROVIDER_URI)) {
1129 return getUnderlyingString(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI);
1130 }
Steve Howard8651bd52010-08-03 12:35:32 -07001131
Steve Howarda2709362010-07-02 17:12:48 -07001132 assert column.equals(COLUMN_LOCAL_URI);
Steve Howardeca77fc2010-09-12 18:49:08 -07001133 return getLocalUri();
1134 }
1135
1136 private String getLocalUri() {
Steve Howardeca77fc2010-09-12 18:49:08 -07001137 long destinationType = getUnderlyingLong(Downloads.Impl.COLUMN_DESTINATION);
1138 if (destinationType == Downloads.Impl.DESTINATION_FILE_URI) {
Steve Howarda9e87c92010-09-16 12:02:03 -07001139 // return client-provided file URI for external download
1140 return getUnderlyingString(Downloads.Impl.COLUMN_FILE_NAME_HINT);
Steve Howardeca77fc2010-09-12 18:49:08 -07001141 }
1142
Steve Howardbb0d23b2010-09-22 18:56:29 -07001143 if (destinationType == Downloads.Impl.DESTINATION_EXTERNAL) {
1144 // return stored destination for legacy external download
Steve Howard99047d72010-09-29 17:41:37 -07001145 String localPath = getUnderlyingString(Downloads.Impl._DATA);
1146 if (localPath == null) {
1147 return null;
1148 }
1149 return Uri.fromFile(new File(localPath)).toString();
Steve Howardbb0d23b2010-09-22 18:56:29 -07001150 }
1151
Steve Howardeca77fc2010-09-12 18:49:08 -07001152 // return content URI for cache download
1153 long downloadId = getUnderlyingLong(Downloads.Impl._ID);
1154 return ContentUris.withAppendedId(mBaseUri, downloadId).toString();
Steve Howarda2709362010-07-02 17:12:48 -07001155 }
1156
1157 private long translateLong(String column) {
1158 if (!isLongColumn(column)) {
1159 // mimic behavior of underlying cursor -- most likely, throw NumberFormatException
1160 return Long.valueOf(translateString(column));
1161 }
1162
1163 if (column.equals(COLUMN_ID)) {
1164 return getUnderlyingLong(Downloads.Impl._ID);
1165 }
1166 if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) {
Vasu Norief7e33b2010-10-20 13:26:02 -07001167 return getUnderlyingLong(Downloads.Impl.COLUMN_TOTAL_BYTES);
Steve Howarda2709362010-07-02 17:12:48 -07001168 }
1169 if (column.equals(COLUMN_STATUS)) {
Vasu Norief7e33b2010-10-20 13:26:02 -07001170 return translateStatus((int) getUnderlyingLong(Downloads.Impl.COLUMN_STATUS));
Steve Howarda2709362010-07-02 17:12:48 -07001171 }
Steve Howard3e8c1d32010-09-29 17:03:32 -07001172 if (column.equals(COLUMN_REASON)) {
Vasu Norief7e33b2010-10-20 13:26:02 -07001173 return getReason((int) getUnderlyingLong(Downloads.Impl.COLUMN_STATUS));
Steve Howarda2709362010-07-02 17:12:48 -07001174 }
1175 if (column.equals(COLUMN_BYTES_DOWNLOADED_SO_FAR)) {
Vasu Norief7e33b2010-10-20 13:26:02 -07001176 return getUnderlyingLong(Downloads.Impl.COLUMN_CURRENT_BYTES);
Steve Howarda2709362010-07-02 17:12:48 -07001177 }
Vasu Nori5be894e2010-11-02 21:55:30 -07001178 if (column.equals(Downloads.Impl.COLUMN_DESTINATION)) {
1179 return getUnderlyingLong(Downloads.Impl.COLUMN_DESTINATION);
1180 }
Steve Howardadcb6972010-07-12 17:09:25 -07001181 assert column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP);
Vasu Norief7e33b2010-10-20 13:26:02 -07001182 return getUnderlyingLong(Downloads.Impl.COLUMN_LAST_MODIFICATION);
Steve Howarda2709362010-07-02 17:12:48 -07001183 }
1184
Steve Howard3e8c1d32010-09-29 17:03:32 -07001185 private long getReason(int status) {
1186 switch (translateStatus(status)) {
1187 case STATUS_FAILED:
1188 return getErrorCode(status);
1189
1190 case STATUS_PAUSED:
1191 return getPausedReason(status);
1192
1193 default:
1194 return 0; // arbitrary value when status is not an error
Steve Howarda2709362010-07-02 17:12:48 -07001195 }
Steve Howard3e8c1d32010-09-29 17:03:32 -07001196 }
1197
1198 private long getPausedReason(int status) {
1199 switch (status) {
1200 case Downloads.Impl.STATUS_WAITING_TO_RETRY:
1201 return PAUSED_WAITING_TO_RETRY;
1202
1203 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
1204 return PAUSED_WAITING_FOR_NETWORK;
1205
1206 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
1207 return PAUSED_QUEUED_FOR_WIFI;
1208
1209 default:
1210 return PAUSED_UNKNOWN;
1211 }
1212 }
1213
1214 private long getErrorCode(int status) {
Steve Howard33bbd122010-08-02 17:51:29 -07001215 if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS)
1216 || (500 <= status && status < 600)) {
Steve Howarda2709362010-07-02 17:12:48 -07001217 // HTTP status code
1218 return status;
1219 }
1220
1221 switch (status) {
Vasu Norief7e33b2010-10-20 13:26:02 -07001222 case Downloads.Impl.STATUS_FILE_ERROR:
Steve Howarda2709362010-07-02 17:12:48 -07001223 return ERROR_FILE_ERROR;
1224
Vasu Norief7e33b2010-10-20 13:26:02 -07001225 case Downloads.Impl.STATUS_UNHANDLED_HTTP_CODE:
1226 case Downloads.Impl.STATUS_UNHANDLED_REDIRECT:
Steve Howarda2709362010-07-02 17:12:48 -07001227 return ERROR_UNHANDLED_HTTP_CODE;
1228
Vasu Norief7e33b2010-10-20 13:26:02 -07001229 case Downloads.Impl.STATUS_HTTP_DATA_ERROR:
Steve Howarda2709362010-07-02 17:12:48 -07001230 return ERROR_HTTP_DATA_ERROR;
1231
Vasu Norief7e33b2010-10-20 13:26:02 -07001232 case Downloads.Impl.STATUS_TOO_MANY_REDIRECTS:
Steve Howarda2709362010-07-02 17:12:48 -07001233 return ERROR_TOO_MANY_REDIRECTS;
1234
Vasu Norief7e33b2010-10-20 13:26:02 -07001235 case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR:
Steve Howarda2709362010-07-02 17:12:48 -07001236 return ERROR_INSUFFICIENT_SPACE;
1237
Vasu Norief7e33b2010-10-20 13:26:02 -07001238 case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR:
Steve Howarda2709362010-07-02 17:12:48 -07001239 return ERROR_DEVICE_NOT_FOUND;
1240
Steve Howard33bbd122010-08-02 17:51:29 -07001241 case Downloads.Impl.STATUS_CANNOT_RESUME:
1242 return ERROR_CANNOT_RESUME;
1243
Steve Howarda9e87c92010-09-16 12:02:03 -07001244 case Downloads.Impl.STATUS_FILE_ALREADY_EXISTS_ERROR:
1245 return ERROR_FILE_ALREADY_EXISTS;
1246
Steve Howarda2709362010-07-02 17:12:48 -07001247 default:
1248 return ERROR_UNKNOWN;
1249 }
1250 }
1251
1252 private long getUnderlyingLong(String column) {
1253 return super.getLong(super.getColumnIndex(column));
1254 }
1255
1256 private String getUnderlyingString(String column) {
1257 return super.getString(super.getColumnIndex(column));
1258 }
1259
Steve Howard3e8c1d32010-09-29 17:03:32 -07001260 private int translateStatus(int status) {
Steve Howarda2709362010-07-02 17:12:48 -07001261 switch (status) {
Vasu Norief7e33b2010-10-20 13:26:02 -07001262 case Downloads.Impl.STATUS_PENDING:
Steve Howarda2709362010-07-02 17:12:48 -07001263 return STATUS_PENDING;
1264
Vasu Norief7e33b2010-10-20 13:26:02 -07001265 case Downloads.Impl.STATUS_RUNNING:
Steve Howarda2709362010-07-02 17:12:48 -07001266 return STATUS_RUNNING;
1267
Steve Howard3e8c1d32010-09-29 17:03:32 -07001268 case Downloads.Impl.STATUS_PAUSED_BY_APP:
1269 case Downloads.Impl.STATUS_WAITING_TO_RETRY:
1270 case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
1271 case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
Steve Howarda2709362010-07-02 17:12:48 -07001272 return STATUS_PAUSED;
1273
Vasu Norief7e33b2010-10-20 13:26:02 -07001274 case Downloads.Impl.STATUS_SUCCESS:
Steve Howarda2709362010-07-02 17:12:48 -07001275 return STATUS_SUCCESSFUL;
1276
1277 default:
Vasu Norief7e33b2010-10-20 13:26:02 -07001278 assert Downloads.Impl.isStatusError(status);
Steve Howarda2709362010-07-02 17:12:48 -07001279 return STATUS_FAILED;
1280 }
1281 }
1282 }
1283}