Downloads.java revision 47f46981cc50f773c445a120da06ae3af866c05f
1/*
2 * Copyright (C) 2008 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.net;
18
19import android.content.ContentResolver;
20import android.content.ContentUris;
21import android.content.ContentValues;
22import android.content.Context;
23import android.database.Cursor;
24import android.net.Uri;
25import android.os.SystemClock;
26import android.provider.BaseColumns;
27import android.util.Log;
28
29import java.io.File;
30
31/**
32 * The Download Manager
33 *
34 * @pending
35 */
36public final class Downloads {
37
38    public static final class ByUri {
39
40        /** @hide */
41        private ByUri() {}
42
43        /**
44          * Initiate a download where the download will be tracked by its URI.
45          * @pending
46          */
47        public static boolean startDownloadByUri(
48                Context context,
49                String url,
50                boolean showDownload,
51                boolean allowRoaming,
52                String title,
53                String notification_package,
54                String notification_class) {
55            ContentResolver cr = context.getContentResolver();
56
57            // Tell download manager to start downloading update.
58            ContentValues values = new ContentValues();
59            values.put(android.provider.Downloads.Impl.COLUMN_URI, url);
60            values.put(android.provider.Downloads.Impl.COLUMN_VISIBILITY,
61                       showDownload ? android.provider.Downloads.Impl.VISIBILITY_VISIBLE
62                       : android.provider.Downloads.Impl.VISIBILITY_HIDDEN);
63            if (title != null) {
64                values.put(android.provider.Downloads.Impl.COLUMN_TITLE, title);
65            }
66            values.put(android.provider.Downloads.Impl.COLUMN_APP_DATA, url);
67            values.put(android.provider.Downloads.Impl.COLUMN_DESTINATION,
68                       allowRoaming ? android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION :
69                       android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING);
70            values.put(android.provider.Downloads.Impl.COLUMN_NO_INTEGRITY, true);  // Don't check ETag
71            if (notification_package != null && notification_class != null) {
72                values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE, notification_package);
73                values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_CLASS, notification_class);
74            }
75
76            if (cr.insert(android.provider.Downloads.Impl.CONTENT_URI, values) == null) {
77                return false;
78            }
79            return true;
80        }
81
82        public static final class StatusInfo {
83            public boolean completed = false;
84            /** The filename of the active download. */
85            public String filename = null;
86            /** An opaque id for the download */
87            public long id = -1;
88            /** An opaque status code for the download */
89            public int statusCode = -1;
90            /** Approximate number of bytes downloaded so far, for debugging purposes. */
91            public long bytesSoFar = -1;
92        }
93
94        /** @hide */
95        private static final int STATUS_INVALID = 0;
96        /** @hide */
97        private static final int STATUS_DOWNLOADING_UPDATE = 3;
98        /** @hide */
99        private static final int STATUS_DOWNLOADED_UPDATE = 4;
100
101        /**
102         * Column projection for the query to the download manager. This must match
103         * with the constants DOWNLOADS_COLUMN_*.
104          * @hide
105         */
106        private static final String[] DOWNLOADS_PROJECTION = {
107            BaseColumns._ID,
108            android.provider.Downloads.Impl.COLUMN_APP_DATA,
109            android.provider.Downloads.Impl.COLUMN_STATUS,
110            android.provider.Downloads.Impl._DATA,
111            android.provider.Downloads.Impl.COLUMN_LAST_MODIFICATION,
112            android.provider.Downloads.Impl.COLUMN_CURRENT_BYTES,
113        };
114
115        /**
116          * The column index for the ID.
117          * @hide
118          */
119        private static final int DOWNLOADS_COLUMN_ID = 0;
120        /**
121          * The column index for the URI.
122          * @hide
123          */
124        private static final int DOWNLOADS_COLUMN_URI = 1;
125        /**
126          * The column index for the status code.
127          * @hide
128          */
129        private static final int DOWNLOADS_COLUMN_STATUS = 2;
130        /**
131          * The column index for the filename.
132          * @hide
133          */
134        private static final int DOWNLOADS_COLUMN_FILENAME = 3;
135        /**
136          * The column index for the last modification time.
137          * @hide
138          */
139        private static final int DOWNLOADS_COLUMN_LAST_MODIFICATION = 4;
140        /**
141          * The column index for the number of bytes downloaded so far.
142          * @hide
143          */
144        private static final int DOWNLOADS_COLUMN_CURRENT_BYTES = 5;
145
146        /**
147         * Gets the status of a download.
148         *
149         * @param c A Cursor pointing to a download.  The URL column is assumed to be valid.
150         * @return The status of the download.
151         * @hide
152         */
153        private static final int getStatusOfDownload(
154                Cursor c,
155                long redownload_threshold) {
156            int status = c.getInt(DOWNLOADS_COLUMN_STATUS);
157            long realtime = SystemClock.elapsedRealtime();
158
159            // TODO(dougz): special handling of 503, 404?  (eg, special
160            // explanatory messages to user)
161
162            if (!android.provider.Downloads.Impl.isStatusCompleted(status)) {
163                // Check if it's stuck
164                long modified = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
165                long now = System.currentTimeMillis();
166                if (now < modified || now - modified > redownload_threshold) {
167                    return STATUS_INVALID;
168                }
169
170                return STATUS_DOWNLOADING_UPDATE;
171            }
172
173            if (android.provider.Downloads.Impl.isStatusError(status)) {
174                return STATUS_INVALID;
175            }
176
177            String filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
178            if (filename == null) {
179                return STATUS_INVALID;
180            }
181
182            return STATUS_DOWNLOADED_UPDATE;
183        }
184
185        /**
186         * Gets a Cursor pointing to the download(s) of the current system update.
187         * @hide
188         */
189        private static final Cursor getCurrentOtaDownloads(Context context, String url) {
190            return context.getContentResolver().query(
191                    android.provider.Downloads.Impl.CONTENT_URI,
192                    DOWNLOADS_PROJECTION,
193                    android.provider.Downloads.Impl.COLUMN_APP_DATA + "='" + url.replace("'", "''") + "'",
194                    null,
195                    null);
196        }
197
198        /**
199         * Returns a StatusInfo with the result of trying to download the
200         * given URL.  Returns null if no attempts have been made.
201         * @pending
202         */
203        public static final StatusInfo getStatus(
204                Context context,
205                String url,
206                long redownload_threshold) {
207            StatusInfo result = null;
208            boolean hasFailedDownload = false;
209            long failedDownloadModificationTime = 0;
210            Cursor c = getCurrentOtaDownloads(context, url);
211            try {
212                while (c != null && c.moveToNext()) {
213                    if (result == null) {
214                        result = new StatusInfo();
215                    }
216                    int status = getStatusOfDownload(c, redownload_threshold);
217                    if (status == STATUS_DOWNLOADING_UPDATE ||
218                        status == STATUS_DOWNLOADED_UPDATE) {
219                        result.completed = (status == STATUS_DOWNLOADED_UPDATE);
220                        result.filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
221                        result.id = c.getLong(DOWNLOADS_COLUMN_ID);
222                        result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
223                        result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
224                        return result;
225                    }
226
227                    long modTime = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
228                    if (hasFailedDownload &&
229                        modTime < failedDownloadModificationTime) {
230                        // older than the one already in result; skip it.
231                        continue;
232                    }
233
234                    hasFailedDownload = true;
235                    failedDownloadModificationTime = modTime;
236                    result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
237                    result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
238                }
239            } finally {
240                c.close();
241            }
242            return result;
243        }
244
245        /**
246         * Query where clause for general querying.
247         * @hide
248         */
249        private static final String QUERY_WHERE_CLAUSE =
250                android.provider.Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE + "=? AND " +
251                android.provider.Downloads.Impl.COLUMN_NOTIFICATION_CLASS + "=?";
252
253        /**
254         * Delete all the downloads for a package/class pair.
255         * @pending
256         */
257        public static final void removeAllDownloadsByPackage(
258                Context context,
259                String notification_package,
260                String notification_class) {
261            context.getContentResolver().delete(
262                    android.provider.Downloads.Impl.CONTENT_URI,
263                    QUERY_WHERE_CLAUSE,
264                    new String[] { notification_package, notification_class });
265        }
266
267        /**
268         * @pending
269         */
270        public static final int getProgressColumnId() {
271            return 0;
272        }
273
274        /**
275         * @pending
276         */
277        public static final int getProgressColumnCurrentBytes() {
278            return 1;
279        }
280
281        /**
282         * @pending
283         */
284        public static final int getProgressColumnTotalBytes() {
285            return 2;
286        }
287
288        /** @hide */
289        private static final String[] PROJECTION = {
290            BaseColumns._ID, android.provider.Downloads.Impl.COLUMN_CURRENT_BYTES, android.provider.Downloads.Impl.COLUMN_TOTAL_BYTES
291        };
292
293        /**
294         * @pending
295         */
296        public static final Cursor getProgressCursor(Context context, long id) {
297            Uri downloadUri = Uri.withAppendedPath(android.provider.Downloads.Impl.CONTENT_URI, String.valueOf(id));
298            return context.getContentResolver().query(downloadUri, PROJECTION, null, null, null);
299        }
300    }
301
302    /**
303     * @hide
304     */
305    private Downloads() {}
306}
307