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