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