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