• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.net;
18 
19 import android.content.ContentResolver;
20 import android.content.ContentUris;
21 import android.content.ContentValues;
22 import android.content.Context;
23 import android.database.Cursor;
24 import android.net.Uri;
25 import android.os.ParcelFileDescriptor;
26 import android.os.SystemClock;
27 import android.provider.BaseColumns;
28 import android.util.Log;
29 
30 import java.io.File;
31 import java.io.FileNotFoundException;
32 import java.io.IOException;
33 import java.io.File;
34 import java.io.InputStream;
35 
36 /**
37  * The Download Manager
38  *
39  * @hide
40  */
41 public final class Downloads {
42 
43 
44     /**
45      * Download status codes
46      */
47 
48     /**
49      * This download hasn't started yet
50      */
51     public static final int STATUS_PENDING = 190;
52 
53     /**
54      * This download has started
55      */
56     public static final int STATUS_RUNNING = 192;
57 
58     /**
59      * This download has successfully completed.
60      * Warning: there might be other status values that indicate success
61      * in the future.
62      * Use isSucccess() to capture the entire category.
63      */
64     public static final int STATUS_SUCCESS = 200;
65 
66     /**
67      * This download can't be performed because the content type cannot be
68      * handled.
69      */
70     public static final int STATUS_NOT_ACCEPTABLE = 406;
71 
72     /**
73      * This download has completed with an error.
74      * Warning: there will be other status values that indicate errors in
75      * the future. Use isStatusError() to capture the entire category.
76      */
77     public static final int STATUS_UNKNOWN_ERROR = 491;
78 
79     /**
80      * This download couldn't be completed because of an HTTP
81      * redirect response that the download manager couldn't
82      * handle.
83      */
84     public static final int STATUS_UNHANDLED_REDIRECT = 493;
85 
86     /**
87      * This download couldn't be completed due to insufficient storage
88      * space.  Typically, this is because the SD card is full.
89      */
90     public static final int STATUS_INSUFFICIENT_SPACE_ERROR = 498;
91 
92     /**
93      * This download couldn't be completed because no external storage
94      * device was found.  Typically, this is because the SD card is not
95      * mounted.
96      */
97     public static final int STATUS_DEVICE_NOT_FOUND_ERROR = 499;
98 
99     /**
100      * Returns whether the status is a success (i.e. 2xx).
101      */
isStatusSuccess(int status)102     public static boolean isStatusSuccess(int status) {
103         return (status >= 200 && status < 300);
104     }
105 
106     /**
107      * Returns whether the status is an error (i.e. 4xx or 5xx).
108      */
isStatusError(int status)109     public static boolean isStatusError(int status) {
110         return (status >= 400 && status < 600);
111     }
112 
113     /**
114      * Download destinations
115      */
116 
117     /**
118      * This download will be saved to the external storage. This is the
119      * default behavior, and should be used for any file that the user
120      * can freely access, copy, delete. Even with that destination,
121      * unencrypted DRM files are saved in secure internal storage.
122      * Downloads to the external destination only write files for which
123      * there is a registered handler. The resulting files are accessible
124      * by filename to all applications.
125      */
126     public static final int DOWNLOAD_DESTINATION_EXTERNAL = 1;
127 
128     /**
129      * This download will be saved to the download manager's private
130      * partition. This is the behavior used by applications that want to
131      * download private files that are used and deleted soon after they
132      * get downloaded. All file types are allowed, and only the initiating
133      * application can access the file (indirectly through a content
134      * provider). This requires the
135      * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED permission.
136      */
137     public static final int DOWNLOAD_DESTINATION_CACHE = 2;
138 
139     /**
140      * This download will be saved to the download manager's private
141      * partition and will be purged as necessary to make space. This is
142      * for private files (similar to CACHE_PARTITION) that aren't deleted
143      * immediately after they are used, and are kept around by the download
144      * manager as long as space is available.
145      */
146     public static final int DOWNLOAD_DESTINATION_CACHE_PURGEABLE = 3;
147 
148 
149     /**
150      * An invalid download id
151      */
152     public static final long DOWNLOAD_ID_INVALID = -1;
153 
154 
155     /**
156      * Broadcast Action: this is sent by the download manager to the app
157      * that had initiated a download when that download completes. The
158      * download's content: uri is specified in the intent's data.
159      */
160     public static final String ACTION_DOWNLOAD_COMPLETED =
161             "android.intent.action.DOWNLOAD_COMPLETED";
162 
163     /**
164      * If extras are specified when requesting a download they will be provided in the intent that
165      * is sent to the specified class and package when a download has finished.
166      * <P>Type: TEXT</P>
167      * <P>Owner can Init</P>
168      */
169     public static final String COLUMN_NOTIFICATION_EXTRAS = "notificationextras";
170 
171 
172     /**
173      * Status class for a download
174      */
175     public static final class StatusInfo {
176         public boolean completed = false;
177         /** The filename of the active download. */
178         public String filename = null;
179         /** An opaque id for the download */
180         public long id = DOWNLOAD_ID_INVALID;
181         /** An opaque status code for the download */
182         public int statusCode = -1;
183         /** Approximate number of bytes downloaded so far, for debugging purposes. */
184         public long bytesSoFar = -1;
185 
186         /**
187          * Returns whether the download is completed
188          * @return a boolean whether the download is complete.
189          */
isComplete()190         public boolean isComplete() {
191             return android.provider.Downloads.Impl.isStatusCompleted(statusCode);
192         }
193 
194         /**
195          * Returns whether the download is successful
196          * @return a boolean whether the download is successful.
197          */
isSuccessful()198         public boolean isSuccessful() {
199             return android.provider.Downloads.Impl.isStatusCompleted(statusCode);
200         }
201     }
202 
203     /**
204      * Class to access initiate and query download by server uri
205      */
206     public static final class ByUri extends DownloadBase {
207         /** @hide */
ByUri()208         private ByUri() {}
209 
210         /**
211          * Query where clause by app data.
212          * @hide
213          */
214         private static final String QUERY_WHERE_APP_DATA_CLAUSE =
215                 android.provider.Downloads.Impl.COLUMN_APP_DATA + "=?";
216 
217         /**
218          * Gets a Cursor pointing to the download(s) of the current system update.
219          * @hide
220          */
getCurrentOtaDownloads(Context context, String url)221         private static final Cursor getCurrentOtaDownloads(Context context, String url) {
222             return context.getContentResolver().query(
223                     android.provider.Downloads.Impl.CONTENT_URI,
224                     DOWNLOADS_PROJECTION,
225                     QUERY_WHERE_APP_DATA_CLAUSE,
226                     new String[] {url},
227                     null);
228         }
229 
230         /**
231          * Returns a StatusInfo with the result of trying to download the
232          * given URL.  Returns null if no attempts have been made.
233          */
getStatus( Context context, String url, long redownload_threshold)234         public static final StatusInfo getStatus(
235                 Context context,
236                 String url,
237                 long redownload_threshold) {
238             StatusInfo result = null;
239             boolean hasFailedDownload = false;
240             long failedDownloadModificationTime = 0;
241             Cursor c = getCurrentOtaDownloads(context, url);
242             try {
243                 while (c != null && c.moveToNext()) {
244                     if (result == null) {
245                         result = new StatusInfo();
246                     }
247                     int status = getStatusOfDownload(c, redownload_threshold);
248                     if (status == STATUS_DOWNLOADING_UPDATE ||
249                         status == STATUS_DOWNLOADED_UPDATE) {
250                         result.completed = (status == STATUS_DOWNLOADED_UPDATE);
251                         result.filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
252                         result.id = c.getLong(DOWNLOADS_COLUMN_ID);
253                         result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
254                         result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
255                         return result;
256                     }
257 
258                     long modTime = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
259                     if (hasFailedDownload &&
260                         modTime < failedDownloadModificationTime) {
261                         // older than the one already in result; skip it.
262                         continue;
263                     }
264 
265                     hasFailedDownload = true;
266                     failedDownloadModificationTime = modTime;
267                     result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
268                     result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
269                 }
270             } finally {
271                 if (c != null) {
272                     c.close();
273                 }
274             }
275             return result;
276         }
277 
278         /**
279          * Query where clause for general querying.
280          */
281         private static final String QUERY_WHERE_CLAUSE =
282                 android.provider.Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE + "=? AND " +
283                 android.provider.Downloads.Impl.COLUMN_NOTIFICATION_CLASS + "=?";
284 
285         /**
286          * Delete all the downloads for a package/class pair.
287          */
removeAllDownloadsByPackage( Context context, String notification_package, String notification_class)288         public static final void removeAllDownloadsByPackage(
289                 Context context,
290                 String notification_package,
291                 String notification_class) {
292             context.getContentResolver().delete(
293                     android.provider.Downloads.Impl.CONTENT_URI,
294                     QUERY_WHERE_CLAUSE,
295                     new String[] { notification_package, notification_class });
296         }
297 
298         /**
299          * The column for the id in the Cursor returned by
300          * getProgressCursor()
301          */
getProgressColumnId()302         public static final int getProgressColumnId() {
303             return 0;
304         }
305 
306         /**
307          * The column for the current byte count in the Cursor returned by
308          * getProgressCursor()
309          */
getProgressColumnCurrentBytes()310         public static final int getProgressColumnCurrentBytes() {
311             return 1;
312         }
313 
314         /**
315          * The column for the total byte count in the Cursor returned by
316          * getProgressCursor()
317          */
getProgressColumnTotalBytes()318         public static final int getProgressColumnTotalBytes() {
319             return 2;
320         }
321 
322         /** @hide */
323         private static final String[] PROJECTION = {
324             BaseColumns._ID,
325             android.provider.Downloads.Impl.COLUMN_CURRENT_BYTES,
326             android.provider.Downloads.Impl.COLUMN_TOTAL_BYTES
327         };
328 
329         /**
330          * Returns a Cursor representing the progress of the download identified by the ID.
331          */
getProgressCursor(Context context, long id)332         public static final Cursor getProgressCursor(Context context, long id) {
333             Uri downloadUri = Uri.withAppendedPath(android.provider.Downloads.Impl.CONTENT_URI,
334                     String.valueOf(id));
335             return context.getContentResolver().query(downloadUri, PROJECTION, null, null, null);
336         }
337     }
338 
339     /**
340      * Class to access downloads by opaque download id
341      */
342     public static final class ById extends DownloadBase {
343         /** @hide */
ById()344         private ById() {}
345 
346         /**
347          * Get the mime tupe of the download specified by the download id
348          */
getMimeTypeForId(Context context, long downloadId)349         public static String getMimeTypeForId(Context context, long downloadId) {
350             ContentResolver cr = context.getContentResolver();
351 
352             String mimeType = null;
353             Cursor downloadCursor = null;
354 
355             try {
356                 Uri downloadUri = getDownloadUri(downloadId);
357 
358                 downloadCursor = cr.query(
359                         downloadUri, new String[]{android.provider.Downloads.Impl.COLUMN_MIME_TYPE},
360                         null, null, null);
361                 if (downloadCursor.moveToNext()) {
362                     mimeType = downloadCursor.getString(0);
363                 }
364             } finally {
365                 if (downloadCursor != null) downloadCursor.close();
366             }
367             return mimeType;
368         }
369 
370         /**
371          * Delete a download by Id
372          */
deleteDownload(Context context, long downloadId)373         public static void deleteDownload(Context context, long downloadId) {
374             ContentResolver cr = context.getContentResolver();
375 
376             String mimeType = null;
377 
378             Uri downloadUri = getDownloadUri(downloadId);
379 
380             cr.delete(downloadUri, null, null);
381         }
382 
383         /**
384          * Open a filedescriptor to a particular download
385          */
openDownload( Context context, long downloadId, String mode)386         public static ParcelFileDescriptor openDownload(
387                 Context context, long downloadId, String mode)
388             throws FileNotFoundException
389         {
390             ContentResolver cr = context.getContentResolver();
391 
392             String mimeType = null;
393 
394             Uri downloadUri = getDownloadUri(downloadId);
395 
396             return cr.openFileDescriptor(downloadUri, mode);
397         }
398 
399         /**
400          * Open a stream to a particular download
401          */
openDownloadStream(Context context, long downloadId)402         public static InputStream openDownloadStream(Context context, long downloadId)
403                 throws FileNotFoundException, IOException
404         {
405             ContentResolver cr = context.getContentResolver();
406 
407             String mimeType = null;
408 
409             Uri downloadUri = getDownloadUri(downloadId);
410 
411             return cr.openInputStream(downloadUri);
412         }
413 
getDownloadUri(long downloadId)414         private static Uri getDownloadUri(long downloadId) {
415             return Uri.parse(android.provider.Downloads.Impl.CONTENT_URI + "/" + downloadId);
416         }
417 
418         /**
419          * Returns a StatusInfo with the result of trying to download the
420          * given URL.  Returns null if no attempts have been made.
421          */
getStatus( Context context, long downloadId)422         public static final StatusInfo getStatus(
423                 Context context,
424                 long downloadId) {
425             StatusInfo result = null;
426             boolean hasFailedDownload = false;
427             long failedDownloadModificationTime = 0;
428 
429             Uri downloadUri = getDownloadUri(downloadId);
430 
431             ContentResolver cr = context.getContentResolver();
432 
433             Cursor c = cr.query(
434                     downloadUri, DOWNLOADS_PROJECTION, null /* selection */, null /* selection args */,
435                     null /* sort order */);
436             try {
437                 if (!c.moveToNext()) {
438                     return result;
439                 }
440 
441                 if (result == null) {
442                     result = new StatusInfo();
443                 }
444                 int status = getStatusOfDownload(c,0);
445                 if (status == STATUS_DOWNLOADING_UPDATE ||
446                         status == STATUS_DOWNLOADED_UPDATE) {
447                     result.completed = (status == STATUS_DOWNLOADED_UPDATE);
448                     result.filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
449                     result.id = c.getLong(DOWNLOADS_COLUMN_ID);
450                     result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
451                     result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
452                     return result;
453                 }
454 
455                 long modTime = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
456 
457                 result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
458                 result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
459             } finally {
460                 if (c != null) {
461                     c.close();
462                 }
463             }
464             return result;
465         }
466     }
467 
468 
469     /**
470      * Base class with common functionality for the various download classes
471      */
472     public static class DownloadBase {
473         /** @hide */
DownloadBase()474         DownloadBase() {}
475 
476         /**
477           * Initiate a download where the download will be tracked by its URI.
478           */
startDownloadByUri( Context context, String url, String cookieData, boolean showDownload, int downloadDestination, boolean allowRoaming, boolean skipIntegrityCheck, String title, String notification_package, String notification_class, String notification_extras)479         public static long startDownloadByUri(
480                 Context context,
481                 String url,
482                 String cookieData,
483                 boolean showDownload,
484                 int downloadDestination,
485                 boolean allowRoaming,
486                 boolean skipIntegrityCheck,
487                 String title,
488                 String notification_package,
489                 String notification_class,
490                 String notification_extras) {
491             ContentResolver cr = context.getContentResolver();
492 
493             // Tell download manager to start downloading update.
494             ContentValues values = new ContentValues();
495             values.put(android.provider.Downloads.Impl.COLUMN_URI, url);
496             values.put(android.provider.Downloads.Impl.COLUMN_COOKIE_DATA, cookieData);
497             values.put(android.provider.Downloads.Impl.COLUMN_VISIBILITY,
498                        showDownload ? android.provider.Downloads.Impl.VISIBILITY_VISIBLE
499                        : android.provider.Downloads.Impl.VISIBILITY_HIDDEN);
500             if (title != null) {
501                 values.put(android.provider.Downloads.Impl.COLUMN_TITLE, title);
502             }
503             values.put(android.provider.Downloads.Impl.COLUMN_APP_DATA, url);
504 
505 
506             // NOTE:  destination should be seperated from whether the download
507             // can happen when roaming
508             int destination = android.provider.Downloads.Impl.DESTINATION_EXTERNAL;
509             switch (downloadDestination) {
510                 case DOWNLOAD_DESTINATION_EXTERNAL:
511                     destination = android.provider.Downloads.Impl.DESTINATION_EXTERNAL;
512                     break;
513                 case DOWNLOAD_DESTINATION_CACHE:
514                     if (allowRoaming) {
515                         destination = android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION;
516                     } else {
517                         destination =
518                                 android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING;
519                     }
520                     break;
521                 case DOWNLOAD_DESTINATION_CACHE_PURGEABLE:
522                     destination =
523                             android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE;
524                     break;
525             }
526             values.put(android.provider.Downloads.Impl.COLUMN_DESTINATION, destination);
527             values.put(android.provider.Downloads.Impl.COLUMN_NO_INTEGRITY,
528                     skipIntegrityCheck);  // Don't check ETag
529             if (notification_package != null && notification_class != null) {
530                 values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE,
531                         notification_package);
532                 values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_CLASS,
533                         notification_class);
534 
535                 if (notification_extras != null) {
536                     values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS,
537                             notification_extras);
538                 }
539             }
540 
541             Uri downloadUri = cr.insert(android.provider.Downloads.Impl.CONTENT_URI, values);
542 
543             long downloadId = DOWNLOAD_ID_INVALID;
544             if (downloadUri != null) {
545                 downloadId = Long.parseLong(downloadUri.getLastPathSegment());
546             }
547             return downloadId;
548         }
549     }
550 
551     /** @hide */
552     private static final int STATUS_INVALID = 0;
553     /** @hide */
554     private static final int STATUS_DOWNLOADING_UPDATE = 3;
555     /** @hide */
556     private static final int STATUS_DOWNLOADED_UPDATE = 4;
557 
558     /**
559      * Column projection for the query to the download manager. This must match
560      * with the constants DOWNLOADS_COLUMN_*.
561      * @hide
562      */
563     private static final String[] DOWNLOADS_PROJECTION = {
564             BaseColumns._ID,
565             android.provider.Downloads.Impl.COLUMN_APP_DATA,
566             android.provider.Downloads.Impl.COLUMN_STATUS,
567             android.provider.Downloads.Impl._DATA,
568             android.provider.Downloads.Impl.COLUMN_LAST_MODIFICATION,
569             android.provider.Downloads.Impl.COLUMN_CURRENT_BYTES,
570     };
571 
572     /**
573      * The column index for the ID.
574      * @hide
575      */
576     private static final int DOWNLOADS_COLUMN_ID = 0;
577     /**
578      * The column index for the URI.
579      * @hide
580      */
581     private static final int DOWNLOADS_COLUMN_URI = 1;
582     /**
583      * The column index for the status code.
584      * @hide
585      */
586     private static final int DOWNLOADS_COLUMN_STATUS = 2;
587     /**
588      * The column index for the filename.
589      * @hide
590      */
591     private static final int DOWNLOADS_COLUMN_FILENAME = 3;
592     /**
593      * The column index for the last modification time.
594      * @hide
595      */
596     private static final int DOWNLOADS_COLUMN_LAST_MODIFICATION = 4;
597     /**
598      * The column index for the number of bytes downloaded so far.
599      * @hide
600      */
601     private static final int DOWNLOADS_COLUMN_CURRENT_BYTES = 5;
602 
603     /**
604      * Gets the status of a download.
605      *
606      * @param c A Cursor pointing to a download.  The URL column is assumed to be valid.
607      * @return The status of the download.
608      * @hide
609      */
getStatusOfDownload( Cursor c, long redownload_threshold)610     private static final int getStatusOfDownload( Cursor c, long redownload_threshold) {
611         int status = c.getInt(DOWNLOADS_COLUMN_STATUS);
612         long realtime = SystemClock.elapsedRealtime();
613 
614         // TODO(dougz): special handling of 503, 404?  (eg, special
615         // explanatory messages to user)
616 
617         if (!android.provider.Downloads.Impl.isStatusCompleted(status)) {
618             // Check if it's stuck
619             long modified = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
620             long now = System.currentTimeMillis();
621             if (now < modified || now - modified > redownload_threshold) {
622                 return STATUS_INVALID;
623             }
624 
625             return STATUS_DOWNLOADING_UPDATE;
626         }
627 
628         if (android.provider.Downloads.Impl.isStatusError(status)) {
629             return STATUS_INVALID;
630         }
631 
632         String filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
633         if (filename == null) {
634             return STATUS_INVALID;
635         }
636 
637         return STATUS_DOWNLOADED_UPDATE;
638     }
639 
640 
641     /**
642      * @hide
643      */
Downloads()644     private Downloads() {}
645 }
646