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