• 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.isStatusSuccess(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(downloadUri, DOWNLOADS_PROJECTION, null /* selection */,
434                     null /* selection args */, null /* sort order */);
435             try {
436                 if (c == null || !c.moveToNext()) {
437                     return result;
438                 }
439 
440                 if (result == null) {
441                     result = new StatusInfo();
442                 }
443                 int status = getStatusOfDownload(c,0);
444                 if (status == STATUS_DOWNLOADING_UPDATE ||
445                         status == STATUS_DOWNLOADED_UPDATE) {
446                     result.completed = (status == STATUS_DOWNLOADED_UPDATE);
447                     result.filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
448                     result.id = c.getLong(DOWNLOADS_COLUMN_ID);
449                     result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
450                     result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
451                     return result;
452                 }
453 
454                 long modTime = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
455 
456                 result.statusCode = c.getInt(DOWNLOADS_COLUMN_STATUS);
457                 result.bytesSoFar = c.getLong(DOWNLOADS_COLUMN_CURRENT_BYTES);
458             } finally {
459                 if (c != null) {
460                     c.close();
461                 }
462             }
463             return result;
464         }
465     }
466 
467 
468     /**
469      * Base class with common functionality for the various download classes
470      */
471     public static class DownloadBase {
472         /** @hide */
DownloadBase()473         DownloadBase() {}
474 
475         /**
476           * Initiate a download where the download will be tracked by its URI.
477           */
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)478         public static long startDownloadByUri(
479                 Context context,
480                 String url,
481                 String cookieData,
482                 boolean showDownload,
483                 int downloadDestination,
484                 boolean allowRoaming,
485                 boolean skipIntegrityCheck,
486                 String title,
487                 String notification_package,
488                 String notification_class,
489                 String notification_extras) {
490             ContentResolver cr = context.getContentResolver();
491 
492             // Tell download manager to start downloading update.
493             ContentValues values = new ContentValues();
494             values.put(android.provider.Downloads.Impl.COLUMN_URI, url);
495             values.put(android.provider.Downloads.Impl.COLUMN_COOKIE_DATA, cookieData);
496             values.put(android.provider.Downloads.Impl.COLUMN_VISIBILITY,
497                        showDownload ? android.provider.Downloads.Impl.VISIBILITY_VISIBLE
498                        : android.provider.Downloads.Impl.VISIBILITY_HIDDEN);
499             if (title != null) {
500                 values.put(android.provider.Downloads.Impl.COLUMN_TITLE, title);
501             }
502             values.put(android.provider.Downloads.Impl.COLUMN_APP_DATA, url);
503 
504 
505             // NOTE:  destination should be seperated from whether the download
506             // can happen when roaming
507             int destination = android.provider.Downloads.Impl.DESTINATION_EXTERNAL;
508             switch (downloadDestination) {
509                 case DOWNLOAD_DESTINATION_EXTERNAL:
510                     destination = android.provider.Downloads.Impl.DESTINATION_EXTERNAL;
511                     break;
512                 case DOWNLOAD_DESTINATION_CACHE:
513                     if (allowRoaming) {
514                         destination = android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION;
515                     } else {
516                         destination =
517                                 android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING;
518                     }
519                     break;
520                 case DOWNLOAD_DESTINATION_CACHE_PURGEABLE:
521                     destination =
522                             android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE;
523                     break;
524             }
525             values.put(android.provider.Downloads.Impl.COLUMN_DESTINATION, destination);
526             values.put(android.provider.Downloads.Impl.COLUMN_NO_INTEGRITY,
527                     skipIntegrityCheck);  // Don't check ETag
528             if (notification_package != null && notification_class != null) {
529                 values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE,
530                         notification_package);
531                 values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_CLASS,
532                         notification_class);
533 
534                 if (notification_extras != null) {
535                     values.put(android.provider.Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS,
536                             notification_extras);
537                 }
538             }
539 
540             Uri downloadUri = cr.insert(android.provider.Downloads.Impl.CONTENT_URI, values);
541 
542             long downloadId = DOWNLOAD_ID_INVALID;
543             if (downloadUri != null) {
544                 downloadId = Long.parseLong(downloadUri.getLastPathSegment());
545             }
546             return downloadId;
547         }
548     }
549 
550     /** @hide */
551     private static final int STATUS_INVALID = 0;
552     /** @hide */
553     private static final int STATUS_DOWNLOADING_UPDATE = 3;
554     /** @hide */
555     private static final int STATUS_DOWNLOADED_UPDATE = 4;
556 
557     /**
558      * Column projection for the query to the download manager. This must match
559      * with the constants DOWNLOADS_COLUMN_*.
560      * @hide
561      */
562     private static final String[] DOWNLOADS_PROJECTION = {
563             BaseColumns._ID,
564             android.provider.Downloads.Impl.COLUMN_APP_DATA,
565             android.provider.Downloads.Impl.COLUMN_STATUS,
566             android.provider.Downloads.Impl._DATA,
567             android.provider.Downloads.Impl.COLUMN_LAST_MODIFICATION,
568             android.provider.Downloads.Impl.COLUMN_CURRENT_BYTES,
569     };
570 
571     /**
572      * The column index for the ID.
573      * @hide
574      */
575     private static final int DOWNLOADS_COLUMN_ID = 0;
576     /**
577      * The column index for the URI.
578      * @hide
579      */
580     private static final int DOWNLOADS_COLUMN_URI = 1;
581     /**
582      * The column index for the status code.
583      * @hide
584      */
585     private static final int DOWNLOADS_COLUMN_STATUS = 2;
586     /**
587      * The column index for the filename.
588      * @hide
589      */
590     private static final int DOWNLOADS_COLUMN_FILENAME = 3;
591     /**
592      * The column index for the last modification time.
593      * @hide
594      */
595     private static final int DOWNLOADS_COLUMN_LAST_MODIFICATION = 4;
596     /**
597      * The column index for the number of bytes downloaded so far.
598      * @hide
599      */
600     private static final int DOWNLOADS_COLUMN_CURRENT_BYTES = 5;
601 
602     /**
603      * Gets the status of a download.
604      *
605      * @param c A Cursor pointing to a download.  The URL column is assumed to be valid.
606      * @return The status of the download.
607      * @hide
608      */
getStatusOfDownload( Cursor c, long redownload_threshold)609     private static final int getStatusOfDownload( Cursor c, long redownload_threshold) {
610         int status = c.getInt(DOWNLOADS_COLUMN_STATUS);
611         long realtime = SystemClock.elapsedRealtime();
612 
613         // TODO(dougz): special handling of 503, 404?  (eg, special
614         // explanatory messages to user)
615 
616         if (!android.provider.Downloads.Impl.isStatusCompleted(status)) {
617             // Check if it's stuck
618             long modified = c.getLong(DOWNLOADS_COLUMN_LAST_MODIFICATION);
619             long now = System.currentTimeMillis();
620             if (now < modified || now - modified > redownload_threshold) {
621                 return STATUS_INVALID;
622             }
623 
624             return STATUS_DOWNLOADING_UPDATE;
625         }
626 
627         if (android.provider.Downloads.Impl.isStatusError(status)) {
628             return STATUS_INVALID;
629         }
630 
631         String filename = c.getString(DOWNLOADS_COLUMN_FILENAME);
632         if (filename == null) {
633             return STATUS_INVALID;
634         }
635 
636         return STATUS_DOWNLOADED_UPDATE;
637     }
638 
639 
640     /**
641      * @hide
642      */
Downloads()643     private Downloads() {}
644 }
645