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