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