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