Home | History | Annotate | Download | only in downloads
      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 
     17 package com.android.providers.downloads;
     18 
     19 import static android.provider.Downloads.Impl.VISIBILITY_VISIBLE;
     20 import static android.provider.Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED;
     21 
     22 import static com.android.providers.downloads.Constants.TAG;
     23 
     24 import android.app.DownloadManager;
     25 import android.app.job.JobInfo;
     26 import android.content.ContentResolver;
     27 import android.content.ContentUris;
     28 import android.content.Context;
     29 import android.content.Intent;
     30 import android.database.Cursor;
     31 import android.net.Uri;
     32 import android.os.Environment;
     33 import android.provider.Downloads;
     34 import android.text.TextUtils;
     35 import android.text.format.DateUtils;
     36 import android.util.Log;
     37 import android.util.Pair;
     38 
     39 import com.android.internal.util.IndentingPrintWriter;
     40 
     41 import java.io.CharArrayWriter;
     42 import java.io.File;
     43 import java.util.ArrayList;
     44 import java.util.Collection;
     45 import java.util.Collections;
     46 import java.util.List;
     47 
     48 /**
     49  * Details about a specific download. Fields should only be mutated by updating
     50  * from database query.
     51  */
     52 public class DownloadInfo {
     53     // TODO: move towards these in-memory objects being sources of truth, and
     54     // periodically pushing to provider.
     55 
     56     public static class Reader {
     57         private ContentResolver mResolver;
     58         private Cursor mCursor;
     59 
     60         public Reader(ContentResolver resolver, Cursor cursor) {
     61             mResolver = resolver;
     62             mCursor = cursor;
     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 = Intent.normalizeMimeType(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(Downloads.Impl.COLUMN_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(Downloads.Impl.COLUMN_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.mFlags = getInt(Downloads.Impl.COLUMN_FLAGS);
     97             info.mTitle = getString(Downloads.Impl.COLUMN_TITLE);
     98             info.mDescription = getString(Downloads.Impl.COLUMN_DESCRIPTION);
     99             info.mBypassRecommendedSizeLimit =
    100                     getInt(Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT);
    101 
    102             synchronized (this) {
    103                 info.mControl = getInt(Downloads.Impl.COLUMN_CONTROL);
    104             }
    105         }
    106 
    107         public void readRequestHeaders(DownloadInfo info) {
    108             info.mRequestHeaders.clear();
    109             Uri headerUri = Uri.withAppendedPath(
    110                     info.getAllDownloadsUri(), Downloads.Impl.RequestHeaders.URI_SEGMENT);
    111             Cursor cursor = mResolver.query(headerUri, null, null, null, null);
    112             try {
    113                 int headerIndex =
    114                         cursor.getColumnIndexOrThrow(Downloads.Impl.RequestHeaders.COLUMN_HEADER);
    115                 int valueIndex =
    116                         cursor.getColumnIndexOrThrow(Downloads.Impl.RequestHeaders.COLUMN_VALUE);
    117                 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
    118                     addHeader(info, cursor.getString(headerIndex), cursor.getString(valueIndex));
    119                 }
    120             } finally {
    121                 cursor.close();
    122             }
    123 
    124             if (info.mCookies != null) {
    125                 addHeader(info, "Cookie", info.mCookies);
    126             }
    127             if (info.mReferer != null) {
    128                 addHeader(info, "Referer", info.mReferer);
    129             }
    130         }
    131 
    132         private void addHeader(DownloadInfo info, String header, String value) {
    133             info.mRequestHeaders.add(Pair.create(header, value));
    134         }
    135 
    136         private String getString(String column) {
    137             int index = mCursor.getColumnIndexOrThrow(column);
    138             String s = mCursor.getString(index);
    139             return (TextUtils.isEmpty(s)) ? null : s;
    140         }
    141 
    142         private Integer getInt(String column) {
    143             return mCursor.getInt(mCursor.getColumnIndexOrThrow(column));
    144         }
    145 
    146         private Long getLong(String column) {
    147             return mCursor.getLong(mCursor.getColumnIndexOrThrow(column));
    148         }
    149     }
    150 
    151     public long mId;
    152     public String mUri;
    153     @Deprecated
    154     public boolean mNoIntegrity;
    155     public String mHint;
    156     public String mFileName;
    157     public String mMimeType;
    158     public int mDestination;
    159     public int mVisibility;
    160     public int mControl;
    161     public int mStatus;
    162     public int mNumFailed;
    163     public int mRetryAfter;
    164     public long mLastMod;
    165     public String mPackage;
    166     public String mClass;
    167     public String mExtras;
    168     public String mCookies;
    169     public String mUserAgent;
    170     public String mReferer;
    171     public long mTotalBytes;
    172     public long mCurrentBytes;
    173     public String mETag;
    174     public int mUid;
    175     public int mMediaScanned;
    176     public boolean mDeleted;
    177     public String mMediaProviderUri;
    178     public boolean mIsPublicApi;
    179     public int mAllowedNetworkTypes;
    180     public boolean mAllowRoaming;
    181     public boolean mAllowMetered;
    182     public int mFlags;
    183     public String mTitle;
    184     public String mDescription;
    185     public int mBypassRecommendedSizeLimit;
    186 
    187     private List<Pair<String, String>> mRequestHeaders = new ArrayList<Pair<String, String>>();
    188 
    189     private final Context mContext;
    190     private final SystemFacade mSystemFacade;
    191 
    192     public DownloadInfo(Context context) {
    193         mContext = context;
    194         mSystemFacade = Helpers.getSystemFacade(context);
    195     }
    196 
    197     public static DownloadInfo queryDownloadInfo(Context context, long downloadId) {
    198         final ContentResolver resolver = context.getContentResolver();
    199         try (Cursor cursor = resolver.query(
    200                 ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, downloadId),
    201                 null, null, null, null)) {
    202             final DownloadInfo.Reader reader = new DownloadInfo.Reader(resolver, cursor);
    203             final DownloadInfo info = new DownloadInfo(context);
    204             if (cursor.moveToFirst()) {
    205                 reader.updateFromDatabase(info);
    206                 reader.readRequestHeaders(info);
    207                 return info;
    208             }
    209         }
    210         return null;
    211     }
    212 
    213     public Collection<Pair<String, String>> getHeaders() {
    214         return Collections.unmodifiableList(mRequestHeaders);
    215     }
    216 
    217     public String getUserAgent() {
    218         if (mUserAgent != null) {
    219             return mUserAgent;
    220         } else {
    221             return Constants.DEFAULT_USER_AGENT;
    222         }
    223     }
    224 
    225     public void sendIntentIfRequested() {
    226         if (mPackage == null) {
    227             return;
    228         }
    229 
    230         Intent intent;
    231         if (mIsPublicApi) {
    232             intent = new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
    233             intent.setPackage(mPackage);
    234             intent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, mId);
    235         } else { // legacy behavior
    236             if (mClass == null) {
    237                 return;
    238             }
    239             intent = new Intent(Downloads.Impl.ACTION_DOWNLOAD_COMPLETED);
    240             intent.setClassName(mPackage, mClass);
    241             if (mExtras != null) {
    242                 intent.putExtra(Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS, mExtras);
    243             }
    244             // We only send the content: URI, for security reasons. Otherwise, malicious
    245             //     applications would have an easier time spoofing download results by
    246             //     sending spoofed intents.
    247             intent.setData(getMyDownloadsUri());
    248         }
    249         mSystemFacade.sendBroadcast(intent);
    250     }
    251 
    252     /**
    253      * Return if this download is visible to the user while running.
    254      */
    255     public boolean isVisible() {
    256         switch (mVisibility) {
    257             case VISIBILITY_VISIBLE:
    258             case VISIBILITY_VISIBLE_NOTIFY_COMPLETED:
    259                 return true;
    260             default:
    261                 return false;
    262         }
    263     }
    264 
    265     /**
    266      * Add random fuzz to the given delay so it's anywhere between 1-1.5x the
    267      * requested delay.
    268      */
    269     private long fuzzDelay(long delay) {
    270         return delay + Helpers.sRandom.nextInt((int) (delay / 2));
    271     }
    272 
    273     /**
    274      * Return minimum latency in milliseconds required before this download is
    275      * allowed to start again.
    276      *
    277      * @see android.app.job.JobInfo.Builder#setMinimumLatency(long)
    278      */
    279     public long getMinimumLatency() {
    280         if (mStatus == Downloads.Impl.STATUS_WAITING_TO_RETRY) {
    281             final long now = mSystemFacade.currentTimeMillis();
    282             final long startAfter;
    283             if (mNumFailed == 0) {
    284                 startAfter = now;
    285             } else if (mRetryAfter > 0) {
    286                 startAfter = mLastMod + fuzzDelay(mRetryAfter);
    287             } else {
    288                 final long delay = (Constants.RETRY_FIRST_DELAY * DateUtils.SECOND_IN_MILLIS
    289                         * (1 << (mNumFailed - 1)));
    290                 startAfter = mLastMod + fuzzDelay(delay);
    291             }
    292             return Math.max(0, startAfter - now);
    293         } else {
    294             return 0;
    295         }
    296     }
    297 
    298     /**
    299      * Return the network type constraint required by this download.
    300      *
    301      * @see android.app.job.JobInfo.Builder#setRequiredNetworkType(int)
    302      */
    303     public int getRequiredNetworkType(long totalBytes) {
    304         if (!mAllowMetered) {
    305             return JobInfo.NETWORK_TYPE_UNMETERED;
    306         }
    307         if (mAllowedNetworkTypes == DownloadManager.Request.NETWORK_WIFI) {
    308             return JobInfo.NETWORK_TYPE_UNMETERED;
    309         }
    310         if (totalBytes > mSystemFacade.getMaxBytesOverMobile()) {
    311             return JobInfo.NETWORK_TYPE_UNMETERED;
    312         }
    313         if (totalBytes > mSystemFacade.getRecommendedMaxBytesOverMobile()
    314                 && mBypassRecommendedSizeLimit == 0) {
    315             return JobInfo.NETWORK_TYPE_UNMETERED;
    316         }
    317         if (!mAllowRoaming) {
    318             return JobInfo.NETWORK_TYPE_NOT_ROAMING;
    319         }
    320         return JobInfo.NETWORK_TYPE_ANY;
    321     }
    322 
    323     /**
    324      * Returns whether this download is ready to be scheduled.
    325      */
    326     public boolean isReadyToSchedule() {
    327         if (mControl == Downloads.Impl.CONTROL_PAUSED) {
    328             // the download is paused, so it's not going to start
    329             return false;
    330         }
    331         switch (mStatus) {
    332             case 0:
    333             case Downloads.Impl.STATUS_PENDING:
    334             case Downloads.Impl.STATUS_RUNNING:
    335             case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
    336             case Downloads.Impl.STATUS_WAITING_TO_RETRY:
    337             case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
    338                 return true;
    339 
    340             case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR:
    341                 // is the media mounted?
    342                 final Uri uri = Uri.parse(mUri);
    343                 if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
    344                     final File file = new File(uri.getPath());
    345                     return Environment.MEDIA_MOUNTED
    346                             .equals(Environment.getExternalStorageState(file));
    347                 } else {
    348                     Log.w(TAG, "Expected file URI on external storage: " + mUri);
    349                     return false;
    350                 }
    351 
    352             default:
    353                 return false;
    354         }
    355     }
    356 
    357     /**
    358      * Returns whether this download has a visible notification after
    359      * completion.
    360      */
    361     public boolean hasCompletionNotification() {
    362         if (!Downloads.Impl.isStatusCompleted(mStatus)) {
    363             return false;
    364         }
    365         if (mVisibility == Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) {
    366             return true;
    367         }
    368         return false;
    369     }
    370 
    371     public boolean isMeteredAllowed(long totalBytes) {
    372         return getRequiredNetworkType(totalBytes) != JobInfo.NETWORK_TYPE_UNMETERED;
    373     }
    374 
    375     public boolean isRoamingAllowed() {
    376         if (mIsPublicApi) {
    377             return mAllowRoaming;
    378         } else { // legacy behavior
    379             return mDestination != Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING;
    380         }
    381     }
    382 
    383     public Uri getMyDownloadsUri() {
    384         return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, mId);
    385     }
    386 
    387     public Uri getAllDownloadsUri() {
    388         return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, mId);
    389     }
    390 
    391     @Override
    392     public String toString() {
    393         final CharArrayWriter writer = new CharArrayWriter();
    394         dump(new IndentingPrintWriter(writer, "  "));
    395         return writer.toString();
    396     }
    397 
    398     public void dump(IndentingPrintWriter pw) {
    399         pw.println("DownloadInfo:");
    400         pw.increaseIndent();
    401 
    402         pw.printPair("mId", mId);
    403         pw.printPair("mLastMod", mLastMod);
    404         pw.printPair("mPackage", mPackage);
    405         pw.printPair("mUid", mUid);
    406         pw.println();
    407 
    408         pw.printPair("mUri", mUri);
    409         pw.println();
    410 
    411         pw.printPair("mMimeType", mMimeType);
    412         pw.printPair("mCookies", (mCookies != null) ? "yes" : "no");
    413         pw.printPair("mReferer", (mReferer != null) ? "yes" : "no");
    414         pw.printPair("mUserAgent", mUserAgent);
    415         pw.println();
    416 
    417         pw.printPair("mFileName", mFileName);
    418         pw.printPair("mDestination", mDestination);
    419         pw.println();
    420 
    421         pw.printPair("mStatus", Downloads.Impl.statusToString(mStatus));
    422         pw.printPair("mCurrentBytes", mCurrentBytes);
    423         pw.printPair("mTotalBytes", mTotalBytes);
    424         pw.println();
    425 
    426         pw.printPair("mNumFailed", mNumFailed);
    427         pw.printPair("mRetryAfter", mRetryAfter);
    428         pw.printPair("mETag", mETag);
    429         pw.printPair("mIsPublicApi", mIsPublicApi);
    430         pw.println();
    431 
    432         pw.printPair("mAllowedNetworkTypes", mAllowedNetworkTypes);
    433         pw.printPair("mAllowRoaming", mAllowRoaming);
    434         pw.printPair("mAllowMetered", mAllowMetered);
    435         pw.printPair("mFlags", mFlags);
    436         pw.println();
    437 
    438         pw.decreaseIndent();
    439     }
    440 
    441     /**
    442      * Returns whether a file should be scanned
    443      */
    444     public boolean shouldScanFile(int status) {
    445         return (mMediaScanned == 0)
    446                 && (mDestination == Downloads.Impl.DESTINATION_EXTERNAL ||
    447                         mDestination == Downloads.Impl.DESTINATION_FILE_URI ||
    448                         mDestination == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD)
    449                 && Downloads.Impl.isStatusSuccess(status);
    450     }
    451 
    452     /**
    453      * Query and return status of requested download.
    454      */
    455     public int queryDownloadStatus() {
    456         return queryDownloadInt(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING);
    457     }
    458 
    459     public int queryDownloadControl() {
    460         return queryDownloadInt(Downloads.Impl.COLUMN_CONTROL, Downloads.Impl.CONTROL_RUN);
    461     }
    462 
    463     public int queryDownloadInt(String columnName, int defaultValue) {
    464         try (Cursor cursor = mContext.getContentResolver().query(getAllDownloadsUri(),
    465                 new String[] { columnName }, null, null, null)) {
    466             if (cursor.moveToFirst()) {
    467                 return cursor.getInt(0);
    468             } else {
    469                 return defaultValue;
    470             }
    471         }
    472     }
    473 }
    474