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