• 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 com.android.providers.downloads;
18 
19 import android.app.DownloadManager;
20 import android.content.ContentResolver;
21 import android.content.ContentUris;
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.database.Cursor;
26 import android.net.ConnectivityManager;
27 import android.net.NetworkInfo;
28 import android.net.NetworkInfo.DetailedState;
29 import android.net.Uri;
30 import android.os.Environment;
31 import android.provider.Downloads;
32 import android.provider.Downloads.Impl;
33 import android.text.TextUtils;
34 import android.util.Log;
35 import android.util.Pair;
36 
37 import com.android.internal.util.IndentingPrintWriter;
38 
39 import java.io.PrintWriter;
40 import java.util.ArrayList;
41 import java.util.Collection;
42 import java.util.Collections;
43 import java.util.List;
44 
45 /**
46  * Stores information about an individual download.
47  */
48 public class DownloadInfo {
49     public static class Reader {
50         private ContentResolver mResolver;
51         private Cursor mCursor;
52 
Reader(ContentResolver resolver, Cursor cursor)53         public Reader(ContentResolver resolver, Cursor cursor) {
54             mResolver = resolver;
55             mCursor = cursor;
56         }
57 
newDownloadInfo(Context context, SystemFacade systemFacade)58         public DownloadInfo newDownloadInfo(Context context, SystemFacade systemFacade) {
59             DownloadInfo info = new DownloadInfo(context, systemFacade);
60             updateFromDatabase(info);
61             readRequestHeaders(info);
62             return info;
63         }
64 
updateFromDatabase(DownloadInfo info)65         public void updateFromDatabase(DownloadInfo info) {
66             info.mId = getLong(Downloads.Impl._ID);
67             info.mUri = getString(Downloads.Impl.COLUMN_URI);
68             info.mNoIntegrity = getInt(Downloads.Impl.COLUMN_NO_INTEGRITY) == 1;
69             info.mHint = getString(Downloads.Impl.COLUMN_FILE_NAME_HINT);
70             info.mFileName = getString(Downloads.Impl._DATA);
71             info.mMimeType = getString(Downloads.Impl.COLUMN_MIME_TYPE);
72             info.mDestination = getInt(Downloads.Impl.COLUMN_DESTINATION);
73             info.mVisibility = getInt(Downloads.Impl.COLUMN_VISIBILITY);
74             info.mStatus = getInt(Downloads.Impl.COLUMN_STATUS);
75             info.mNumFailed = getInt(Constants.FAILED_CONNECTIONS);
76             int retryRedirect = getInt(Constants.RETRY_AFTER_X_REDIRECT_COUNT);
77             info.mRetryAfter = retryRedirect & 0xfffffff;
78             info.mLastMod = getLong(Downloads.Impl.COLUMN_LAST_MODIFICATION);
79             info.mPackage = getString(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE);
80             info.mClass = getString(Downloads.Impl.COLUMN_NOTIFICATION_CLASS);
81             info.mExtras = getString(Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS);
82             info.mCookies = getString(Downloads.Impl.COLUMN_COOKIE_DATA);
83             info.mUserAgent = getString(Downloads.Impl.COLUMN_USER_AGENT);
84             info.mReferer = getString(Downloads.Impl.COLUMN_REFERER);
85             info.mTotalBytes = getLong(Downloads.Impl.COLUMN_TOTAL_BYTES);
86             info.mCurrentBytes = getLong(Downloads.Impl.COLUMN_CURRENT_BYTES);
87             info.mETag = getString(Constants.ETAG);
88             info.mUid = getInt(Constants.UID);
89             info.mMediaScanned = getInt(Constants.MEDIA_SCANNED);
90             info.mDeleted = getInt(Downloads.Impl.COLUMN_DELETED) == 1;
91             info.mMediaProviderUri = getString(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI);
92             info.mIsPublicApi = getInt(Downloads.Impl.COLUMN_IS_PUBLIC_API) != 0;
93             info.mAllowedNetworkTypes = getInt(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES);
94             info.mAllowRoaming = getInt(Downloads.Impl.COLUMN_ALLOW_ROAMING) != 0;
95             info.mAllowMetered = getInt(Downloads.Impl.COLUMN_ALLOW_METERED) != 0;
96             info.mTitle = getString(Downloads.Impl.COLUMN_TITLE);
97             info.mDescription = getString(Downloads.Impl.COLUMN_DESCRIPTION);
98             info.mBypassRecommendedSizeLimit =
99                     getInt(Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT);
100 
101             synchronized (this) {
102                 info.mControl = getInt(Downloads.Impl.COLUMN_CONTROL);
103             }
104         }
105 
readRequestHeaders(DownloadInfo info)106         private void readRequestHeaders(DownloadInfo info) {
107             info.mRequestHeaders.clear();
108             Uri headerUri = Uri.withAppendedPath(
109                     info.getAllDownloadsUri(), Downloads.Impl.RequestHeaders.URI_SEGMENT);
110             Cursor cursor = mResolver.query(headerUri, null, null, null, null);
111             try {
112                 int headerIndex =
113                         cursor.getColumnIndexOrThrow(Downloads.Impl.RequestHeaders.COLUMN_HEADER);
114                 int valueIndex =
115                         cursor.getColumnIndexOrThrow(Downloads.Impl.RequestHeaders.COLUMN_VALUE);
116                 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
117                     addHeader(info, cursor.getString(headerIndex), cursor.getString(valueIndex));
118                 }
119             } finally {
120                 cursor.close();
121             }
122 
123             if (info.mCookies != null) {
124                 addHeader(info, "Cookie", info.mCookies);
125             }
126             if (info.mReferer != null) {
127                 addHeader(info, "Referer", info.mReferer);
128             }
129         }
130 
addHeader(DownloadInfo info, String header, String value)131         private void addHeader(DownloadInfo info, String header, String value) {
132             info.mRequestHeaders.add(Pair.create(header, value));
133         }
134 
getString(String column)135         private String getString(String column) {
136             int index = mCursor.getColumnIndexOrThrow(column);
137             String s = mCursor.getString(index);
138             return (TextUtils.isEmpty(s)) ? null : s;
139         }
140 
getInt(String column)141         private Integer getInt(String column) {
142             return mCursor.getInt(mCursor.getColumnIndexOrThrow(column));
143         }
144 
getLong(String column)145         private Long getLong(String column) {
146             return mCursor.getLong(mCursor.getColumnIndexOrThrow(column));
147         }
148     }
149 
150     // the following NETWORK_* constants are used to indicates specfic reasons for disallowing a
151     // download from using a network, since specific causes can require special handling
152 
153     /**
154      * The network is usable for the given download.
155      */
156     public static final int NETWORK_OK = 1;
157 
158     /**
159      * There is no network connectivity.
160      */
161     public static final int NETWORK_NO_CONNECTION = 2;
162 
163     /**
164      * The download exceeds the maximum size for this network.
165      */
166     public static final int NETWORK_UNUSABLE_DUE_TO_SIZE = 3;
167 
168     /**
169      * The download exceeds the recommended maximum size for this network, the user must confirm for
170      * this download to proceed without WiFi.
171      */
172     public static final int NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE = 4;
173 
174     /**
175      * The current connection is roaming, and the download can't proceed over a roaming connection.
176      */
177     public static final int NETWORK_CANNOT_USE_ROAMING = 5;
178 
179     /**
180      * The app requesting the download specific that it can't use the current network connection.
181      */
182     public static final int NETWORK_TYPE_DISALLOWED_BY_REQUESTOR = 6;
183 
184     /**
185      * Current network is blocked for requesting application.
186      */
187     public static final int NETWORK_BLOCKED = 7;
188 
189     /**
190      * For intents used to notify the user that a download exceeds a size threshold, if this extra
191      * is true, WiFi is required for this download size; otherwise, it is only recommended.
192      */
193     public static final String EXTRA_IS_WIFI_REQUIRED = "isWifiRequired";
194 
195 
196     public long mId;
197     public String mUri;
198     public boolean mNoIntegrity;
199     public String mHint;
200     public String mFileName;
201     public String mMimeType;
202     public int mDestination;
203     public int mVisibility;
204     public int mControl;
205     public int mStatus;
206     public int mNumFailed;
207     public int mRetryAfter;
208     public long mLastMod;
209     public String mPackage;
210     public String mClass;
211     public String mExtras;
212     public String mCookies;
213     public String mUserAgent;
214     public String mReferer;
215     public long mTotalBytes;
216     public long mCurrentBytes;
217     public String mETag;
218     public int mUid;
219     public int mMediaScanned;
220     public boolean mDeleted;
221     public String mMediaProviderUri;
222     public boolean mIsPublicApi;
223     public int mAllowedNetworkTypes;
224     public boolean mAllowRoaming;
225     public boolean mAllowMetered;
226     public String mTitle;
227     public String mDescription;
228     public int mBypassRecommendedSizeLimit;
229 
230     public int mFuzz;
231 
232     private List<Pair<String, String>> mRequestHeaders = new ArrayList<Pair<String, String>>();
233     private SystemFacade mSystemFacade;
234     private Context mContext;
235 
DownloadInfo(Context context, SystemFacade systemFacade)236     private DownloadInfo(Context context, SystemFacade systemFacade) {
237         mContext = context;
238         mSystemFacade = systemFacade;
239         mFuzz = Helpers.sRandom.nextInt(1001);
240     }
241 
getHeaders()242     public Collection<Pair<String, String>> getHeaders() {
243         return Collections.unmodifiableList(mRequestHeaders);
244     }
245 
sendIntentIfRequested()246     public void sendIntentIfRequested() {
247         if (mPackage == null) {
248             return;
249         }
250 
251         Intent intent;
252         if (mIsPublicApi) {
253             intent = new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
254             intent.setPackage(mPackage);
255             intent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, mId);
256         } else { // legacy behavior
257             if (mClass == null) {
258                 return;
259             }
260             intent = new Intent(Downloads.Impl.ACTION_DOWNLOAD_COMPLETED);
261             intent.setClassName(mPackage, mClass);
262             if (mExtras != null) {
263                 intent.putExtra(Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS, mExtras);
264             }
265             // We only send the content: URI, for security reasons. Otherwise, malicious
266             //     applications would have an easier time spoofing download results by
267             //     sending spoofed intents.
268             intent.setData(getMyDownloadsUri());
269         }
270         mSystemFacade.sendBroadcast(intent);
271     }
272 
273     /**
274      * Returns the time when a download should be restarted.
275      */
restartTime(long now)276     public long restartTime(long now) {
277         if (mNumFailed == 0) {
278             return now;
279         }
280         if (mRetryAfter > 0) {
281             return mLastMod + mRetryAfter;
282         }
283         return mLastMod +
284                 Constants.RETRY_FIRST_DELAY *
285                     (1000 + mFuzz) * (1 << (mNumFailed - 1));
286     }
287 
288     /**
289      * Returns whether this download (which the download manager hasn't seen yet)
290      * should be started.
291      */
isReadyToStart(long now)292     private boolean isReadyToStart(long now) {
293         if (DownloadHandler.getInstance().hasDownloadInQueue(mId)) {
294             // already running
295             return false;
296         }
297         if (mControl == Downloads.Impl.CONTROL_PAUSED) {
298             // the download is paused, so it's not going to start
299             return false;
300         }
301         switch (mStatus) {
302             case 0: // status hasn't been initialized yet, this is a new download
303             case Downloads.Impl.STATUS_PENDING: // download is explicit marked as ready to start
304             case Downloads.Impl.STATUS_RUNNING: // download interrupted (process killed etc) while
305                                                 // running, without a chance to update the database
306                 return true;
307 
308             case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
309             case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
310                 return checkCanUseNetwork() == NETWORK_OK;
311 
312             case Downloads.Impl.STATUS_WAITING_TO_RETRY:
313                 // download was waiting for a delayed restart
314                 return restartTime(now) <= now;
315             case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR:
316                 // is the media mounted?
317                 return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
318             case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR:
319                 // should check space to make sure it is worth retrying the download.
320                 // but thats the first thing done by the thread when it retries to download
321                 // it will fail pretty quickly if there is no space.
322                 // so, it is not that bad to skip checking space availability here.
323                 return true;
324         }
325         return false;
326     }
327 
328     /**
329      * Returns whether this download has a visible notification after
330      * completion.
331      */
hasCompletionNotification()332     public boolean hasCompletionNotification() {
333         if (!Downloads.Impl.isStatusCompleted(mStatus)) {
334             return false;
335         }
336         if (mVisibility == Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) {
337             return true;
338         }
339         return false;
340     }
341 
342     /**
343      * Returns whether this download is allowed to use the network.
344      * @return one of the NETWORK_* constants
345      */
checkCanUseNetwork()346     public int checkCanUseNetwork() {
347         final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mUid);
348         if (info == null) {
349             return NETWORK_NO_CONNECTION;
350         }
351         if (DetailedState.BLOCKED.equals(info.getDetailedState())) {
352             return NETWORK_BLOCKED;
353         }
354         if (!isRoamingAllowed() && mSystemFacade.isNetworkRoaming()) {
355             return NETWORK_CANNOT_USE_ROAMING;
356         }
357         if (!mAllowMetered && mSystemFacade.isActiveNetworkMetered()) {
358             return NETWORK_TYPE_DISALLOWED_BY_REQUESTOR;
359         }
360         return checkIsNetworkTypeAllowed(info.getType());
361     }
362 
isRoamingAllowed()363     private boolean isRoamingAllowed() {
364         if (mIsPublicApi) {
365             return mAllowRoaming;
366         } else { // legacy behavior
367             return mDestination != Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING;
368         }
369     }
370 
371     /**
372      * @return a non-localized string appropriate for logging corresponding to one of the
373      * NETWORK_* constants.
374      */
getLogMessageForNetworkError(int networkError)375     public String getLogMessageForNetworkError(int networkError) {
376         switch (networkError) {
377             case NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE:
378                 return "download size exceeds recommended limit for mobile network";
379 
380             case NETWORK_UNUSABLE_DUE_TO_SIZE:
381                 return "download size exceeds limit for mobile network";
382 
383             case NETWORK_NO_CONNECTION:
384                 return "no network connection available";
385 
386             case NETWORK_CANNOT_USE_ROAMING:
387                 return "download cannot use the current network connection because it is roaming";
388 
389             case NETWORK_TYPE_DISALLOWED_BY_REQUESTOR:
390                 return "download was requested to not use the current network type";
391 
392             case NETWORK_BLOCKED:
393                 return "network is blocked for requesting application";
394 
395             default:
396                 return "unknown error with network connectivity";
397         }
398     }
399 
400     /**
401      * Check if this download can proceed over the given network type.
402      * @param networkType a constant from ConnectivityManager.TYPE_*.
403      * @return one of the NETWORK_* constants
404      */
checkIsNetworkTypeAllowed(int networkType)405     private int checkIsNetworkTypeAllowed(int networkType) {
406         if (mIsPublicApi) {
407             final int flag = translateNetworkTypeToApiFlag(networkType);
408             final boolean allowAllNetworkTypes = mAllowedNetworkTypes == ~0;
409             if (!allowAllNetworkTypes && (flag & mAllowedNetworkTypes) == 0) {
410                 return NETWORK_TYPE_DISALLOWED_BY_REQUESTOR;
411             }
412         }
413         return checkSizeAllowedForNetwork(networkType);
414     }
415 
416     /**
417      * Translate a ConnectivityManager.TYPE_* constant to the corresponding
418      * DownloadManager.Request.NETWORK_* bit flag.
419      */
translateNetworkTypeToApiFlag(int networkType)420     private int translateNetworkTypeToApiFlag(int networkType) {
421         switch (networkType) {
422             case ConnectivityManager.TYPE_MOBILE:
423                 return DownloadManager.Request.NETWORK_MOBILE;
424 
425             case ConnectivityManager.TYPE_WIFI:
426                 return DownloadManager.Request.NETWORK_WIFI;
427 
428             default:
429                 return 0;
430         }
431     }
432 
433     /**
434      * Check if the download's size prohibits it from running over the current network.
435      * @return one of the NETWORK_* constants
436      */
checkSizeAllowedForNetwork(int networkType)437     private int checkSizeAllowedForNetwork(int networkType) {
438         if (mTotalBytes <= 0) {
439             return NETWORK_OK; // we don't know the size yet
440         }
441         if (networkType == ConnectivityManager.TYPE_WIFI) {
442             return NETWORK_OK; // anything goes over wifi
443         }
444         Long maxBytesOverMobile = mSystemFacade.getMaxBytesOverMobile();
445         if (maxBytesOverMobile != null && mTotalBytes > maxBytesOverMobile) {
446             return NETWORK_UNUSABLE_DUE_TO_SIZE;
447         }
448         if (mBypassRecommendedSizeLimit == 0) {
449             Long recommendedMaxBytesOverMobile = mSystemFacade.getRecommendedMaxBytesOverMobile();
450             if (recommendedMaxBytesOverMobile != null
451                     && mTotalBytes > recommendedMaxBytesOverMobile) {
452                 return NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE;
453             }
454         }
455         return NETWORK_OK;
456     }
457 
startIfReady(long now, StorageManager storageManager)458     void startIfReady(long now, StorageManager storageManager) {
459         if (!isReadyToStart(now)) {
460             return;
461         }
462 
463         if (Constants.LOGV) {
464             Log.v(Constants.TAG, "Service spawning thread to handle download " + mId);
465         }
466         if (mStatus != Impl.STATUS_RUNNING) {
467             mStatus = Impl.STATUS_RUNNING;
468             ContentValues values = new ContentValues();
469             values.put(Impl.COLUMN_STATUS, mStatus);
470             mContext.getContentResolver().update(getAllDownloadsUri(), values, null, null);
471         }
472         DownloadHandler.getInstance().enqueueDownload(this);
473     }
474 
isOnCache()475     public boolean isOnCache() {
476         return (mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION
477                 || mDestination == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION
478                 || mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING
479                 || mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE);
480     }
481 
getMyDownloadsUri()482     public Uri getMyDownloadsUri() {
483         return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, mId);
484     }
485 
getAllDownloadsUri()486     public Uri getAllDownloadsUri() {
487         return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, mId);
488     }
489 
dump(IndentingPrintWriter pw)490     public void dump(IndentingPrintWriter pw) {
491         pw.println("DownloadInfo:");
492         pw.increaseIndent();
493 
494         pw.printPair("mId", mId);
495         pw.printPair("mLastMod", mLastMod);
496         pw.printPair("mPackage", mPackage);
497         pw.printPair("mUid", mUid);
498         pw.println();
499 
500         pw.printPair("mUri", mUri);
501         pw.println();
502 
503         pw.printPair("mMimeType", mMimeType);
504         pw.printPair("mCookies", (mCookies != null) ? "yes" : "no");
505         pw.printPair("mReferer", (mReferer != null) ? "yes" : "no");
506         pw.printPair("mUserAgent", mUserAgent);
507         pw.println();
508 
509         pw.printPair("mFileName", mFileName);
510         pw.printPair("mDestination", mDestination);
511         pw.println();
512 
513         pw.printPair("mStatus", Downloads.Impl.statusToString(mStatus));
514         pw.printPair("mCurrentBytes", mCurrentBytes);
515         pw.printPair("mTotalBytes", mTotalBytes);
516         pw.println();
517 
518         pw.printPair("mNumFailed", mNumFailed);
519         pw.printPair("mRetryAfter", mRetryAfter);
520         pw.printPair("mETag", mETag);
521         pw.printPair("mIsPublicApi", mIsPublicApi);
522         pw.println();
523 
524         pw.printPair("mAllowedNetworkTypes", mAllowedNetworkTypes);
525         pw.printPair("mAllowRoaming", mAllowRoaming);
526         pw.printPair("mAllowMetered", mAllowMetered);
527         pw.println();
528 
529         pw.decreaseIndent();
530     }
531 
532     /**
533      * Returns the amount of time (as measured from the "now" parameter)
534      * at which a download will be active.
535      * 0 = immediately - service should stick around to handle this download.
536      * -1 = never - service can go away without ever waking up.
537      * positive value - service must wake up in the future, as specified in ms from "now"
538      */
nextAction(long now)539     long nextAction(long now) {
540         if (Downloads.Impl.isStatusCompleted(mStatus)) {
541             return -1;
542         }
543         if (mStatus != Downloads.Impl.STATUS_WAITING_TO_RETRY) {
544             return 0;
545         }
546         long when = restartTime(now);
547         if (when <= now) {
548             return 0;
549         }
550         return when - now;
551     }
552 
553     /**
554      * Returns whether a file should be scanned
555      */
shouldScanFile()556     boolean shouldScanFile() {
557         return (mMediaScanned == 0)
558                 && (mDestination == Downloads.Impl.DESTINATION_EXTERNAL ||
559                         mDestination == Downloads.Impl.DESTINATION_FILE_URI ||
560                         mDestination == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD)
561                 && Downloads.Impl.isStatusSuccess(mStatus);
562     }
563 
notifyPauseDueToSize(boolean isWifiRequired)564     void notifyPauseDueToSize(boolean isWifiRequired) {
565         Intent intent = new Intent(Intent.ACTION_VIEW);
566         intent.setData(getAllDownloadsUri());
567         intent.setClassName(SizeLimitActivity.class.getPackage().getName(),
568                 SizeLimitActivity.class.getName());
569         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
570         intent.putExtra(EXTRA_IS_WIFI_REQUIRED, isWifiRequired);
571         mContext.startActivity(intent);
572     }
573 
startDownloadThread()574     void startDownloadThread() {
575         DownloadThread downloader = new DownloadThread(mContext, mSystemFacade, this,
576                 StorageManager.getInstance(mContext));
577         mSystemFacade.startThread(downloader);
578     }
579 }
580