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