diff options
Diffstat (limited to 'src/com/android/providers/downloads')
9 files changed, 46 insertions, 240 deletions
diff --git a/src/com/android/providers/downloads/DownloadInfo.java b/src/com/android/providers/downloads/DownloadInfo.java index c571de4d..9ad7e755 100644 --- a/src/com/android/providers/downloads/DownloadInfo.java +++ b/src/com/android/providers/downloads/DownloadInfo.java @@ -380,13 +380,6 @@ public class DownloadInfo { } } - public boolean isOnCache() { - return (mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION - || mDestination == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION - || mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING - || mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE); - } - public Uri getMyDownloadsUri() { return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, mId); } diff --git a/src/com/android/providers/downloads/DownloadNotifier.java b/src/com/android/providers/downloads/DownloadNotifier.java index b7016fcb..d38aa755 100644 --- a/src/com/android/providers/downloads/DownloadNotifier.java +++ b/src/com/android/providers/downloads/DownloadNotifier.java @@ -229,6 +229,7 @@ public class DownloadNotifier { final Uri uri = new Uri.Builder().scheme("active-dl").appendPath(tag).build(); final Intent intent = new Intent(Constants.ACTION_LIST, uri, mContext, DownloadReceiver.class); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); intent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, downloadIds); builder.setContentIntent(PendingIntent.getBroadcast(mContext, @@ -241,6 +242,7 @@ public class DownloadNotifier { final Uri cancelUri = new Uri.Builder().scheme("cancel-dl").appendPath(tag).build(); final Intent cancelIntent = new Intent(Constants.ACTION_CANCEL, cancelUri, mContext, DownloadReceiver.class); + cancelIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); cancelIntent.putExtra(DownloadReceiver.EXTRA_CANCELED_DOWNLOAD_IDS, downloadIds); cancelIntent.putExtra(DownloadReceiver.EXTRA_CANCELED_DOWNLOAD_NOTIFICATION_TAG, tag); @@ -264,14 +266,11 @@ public class DownloadNotifier { if (Downloads.Impl.isStatusError(status)) { action = Constants.ACTION_LIST; } else { - if (destination != Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION) { - action = Constants.ACTION_OPEN; - } else { - action = Constants.ACTION_LIST; - } + action = Constants.ACTION_OPEN; } final Intent intent = new Intent(action, uri, mContext, DownloadReceiver.class); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); intent.putExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS, getDownloadIds(cursor, cluster)); builder.setContentIntent(PendingIntent.getBroadcast(mContext, @@ -279,6 +278,7 @@ public class DownloadNotifier { final Intent hideIntent = new Intent(Constants.ACTION_HIDE, uri, mContext, DownloadReceiver.class); + hideIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); builder.setDeleteIntent(PendingIntent.getBroadcast(mContext, 0, hideIntent, 0)); } diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java index cfd37708..55a87a2f 100644 --- a/src/com/android/providers/downloads/DownloadProvider.java +++ b/src/com/android/providers/downloads/DownloadProvider.java @@ -83,6 +83,8 @@ public final class DownloadProvider extends ContentProvider { private static final int DB_VERSION = 110; /** Name of table in the database */ private static final String DB_TABLE = "downloads"; + /** Memory optimization - close idle connections after 30s of inactivity */ + private static final int IDLE_CONNECTION_TIMEOUT_MS = 30000; /** MIME type for the entire download list */ private static final String DOWNLOAD_LIST_TYPE = "vnd.android.cursor.dir/download"; @@ -249,6 +251,7 @@ public final class DownloadProvider extends ContentProvider { private final class DatabaseHelper extends SQLiteOpenHelper { public DatabaseHelper(final Context context) { super(context, DB_NAME, null, DB_VERSION); + setIdleConnectionTimeout(IDLE_CONNECTION_TIMEOUT_MS); } /** @@ -593,8 +596,7 @@ public final class DownloadProvider extends ContentProvider { if (getContext().checkCallingOrSelfPermission(Downloads.Impl.PERMISSION_ACCESS_ADVANCED) != PackageManager.PERMISSION_GRANTED && (dest == Downloads.Impl.DESTINATION_CACHE_PARTITION - || dest == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING - || dest == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION)) { + || dest == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING)) { throw new SecurityException("setting destination to : " + dest + " not allowed, unless PERMISSION_ACCESS_ADVANCED is granted"); } @@ -621,12 +623,6 @@ public final class DownloadProvider extends ContentProvider { getCallingPackage()) != AppOpsManager.MODE_ALLOWED) { throw new SecurityException("No permission to write"); } - - } else if (dest == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION) { - getContext().enforcePermission( - android.Manifest.permission.ACCESS_CACHE_FILESYSTEM, - Binder.getCallingPid(), Binder.getCallingUid(), - "need ACCESS_CACHE_FILESYSTEM permission to use system cache"); } filteredValues.put(Downloads.Impl.COLUMN_DESTINATION, dest); } @@ -1301,6 +1297,8 @@ public final class DownloadProvider extends ContentProvider { try { getContext().getContentResolver().delete(Uri.parse(mediaUri), null, null); + } catch (Exception e) { + Log.w(Constants.TAG, "Failed to delete media entry: " + e); } finally { Binder.restoreCallingIdentity(token); } diff --git a/src/com/android/providers/downloads/DownloadStorageProvider.java b/src/com/android/providers/downloads/DownloadStorageProvider.java index bbcb06d2..92f4c021 100644 --- a/src/com/android/providers/downloads/DownloadStorageProvider.java +++ b/src/com/android/providers/downloads/DownloadStorageProvider.java @@ -382,14 +382,6 @@ public class DownloadStorageProvider extends FileSystemProvider { } @Override - public AssetFileDescriptor openDocumentThumbnail( - String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException { - // TODO: extend ExifInterface to support fds - final ParcelFileDescriptor pfd = openDocument(docId, "r", signal); - return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH); - } - - @Override protected File getFileForDocId(String docId, boolean visible) throws FileNotFoundException { if (RawDocumentsHelper.isRawDocId(docId)) { return new File(RawDocumentsHelper.getAbsoluteFilePath(docId)); diff --git a/src/com/android/providers/downloads/DownloadThread.java b/src/com/android/providers/downloads/DownloadThread.java index e101c74d..d3ec568c 100644 --- a/src/com/android/providers/downloads/DownloadThread.java +++ b/src/com/android/providers/downloads/DownloadThread.java @@ -25,6 +25,7 @@ import static android.provider.Downloads.Impl.STATUS_CANCELED; import static android.provider.Downloads.Impl.STATUS_CANNOT_RESUME; import static android.provider.Downloads.Impl.STATUS_FILE_ERROR; import static android.provider.Downloads.Impl.STATUS_HTTP_DATA_ERROR; +import static android.provider.Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR; import static android.provider.Downloads.Impl.STATUS_PAUSED_BY_APP; import static android.provider.Downloads.Impl.STATUS_QUEUED_FOR_WIFI; import static android.provider.Downloads.Impl.STATUS_RUNNING; @@ -47,7 +48,6 @@ import static java.net.HttpURLConnection.HTTP_PRECON_FAILED; import static java.net.HttpURLConnection.HTTP_SEE_OTHER; import static java.net.HttpURLConnection.HTTP_UNAVAILABLE; -import android.app.job.JobInfo; import android.app.job.JobParameters; import android.content.ContentValues; import android.content.Context; @@ -64,6 +64,7 @@ import android.net.Uri; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.SystemClock; +import android.os.storage.StorageManager; import android.provider.Downloads; import android.system.ErrnoException; import android.system.Os; @@ -117,6 +118,7 @@ public class DownloadThread extends Thread { private final SystemFacade mSystemFacade; private final DownloadNotifier mNotifier; private final NetworkPolicyManager mNetworkPolicy; + private final StorageManager mStorage; private final DownloadJobService mJobService; private final JobParameters mParams; @@ -245,6 +247,7 @@ public class DownloadThread extends Thread { mSystemFacade = Helpers.getSystemFacade(mContext); mNotifier = Helpers.getDownloadNotifier(mContext); mNetworkPolicy = mContext.getSystemService(NetworkPolicyManager.class); + mStorage = mContext.getSystemService(StorageManager.class); mJobService = service; mParams = params; @@ -564,35 +567,23 @@ public class DownloadThread extends Thread { out = new ParcelFileDescriptor.AutoCloseOutputStream(outPfd); } - // Pre-flight disk space requirements, when known - if (mInfoDelta.mTotalBytes > 0) { - final long curSize = Os.fstat(outFd).st_size; - final long newBytes = mInfoDelta.mTotalBytes - curSize; - - StorageUtils.ensureAvailableSpace(mContext, outFd, newBytes); - - try { - // We found enough space, so claim it for ourselves - Os.posix_fallocate(outFd, 0, mInfoDelta.mTotalBytes); - } catch (ErrnoException e) { - if (e.errno == OsConstants.ENOSYS || e.errno == OsConstants.ENOTSUP) { - Log.w(TAG, "fallocate() not supported; falling back to ftruncate()"); - Os.ftruncate(outFd, mInfoDelta.mTotalBytes); - } else { - throw e; - } - } - } - // Move into place to begin writing Os.lseek(outFd, mInfoDelta.mCurrentBytes, OsConstants.SEEK_SET); - } catch (ErrnoException e) { throw new StopRequestException(STATUS_FILE_ERROR, e); } catch (IOException e) { throw new StopRequestException(STATUS_FILE_ERROR, e); } + try { + // Pre-flight disk space requirements, when known + if (mInfoDelta.mTotalBytes > 0 && mStorage.isAllocationSupported(outFd)) { + mStorage.allocateBytes(outFd, mInfoDelta.mTotalBytes); + } + } catch (IOException e) { + throw new StopRequestException(STATUS_INSUFFICIENT_SPACE_ERROR, e); + } + // Start streaming data, periodically watch for pause/cancel // commands and checking disk space as needed. transferData(in, out, outFd); @@ -650,14 +641,6 @@ public class DownloadThread extends Thread { } try { - // When streaming, ensure space before each write - if (mInfoDelta.mTotalBytes == -1) { - final long curSize = Os.fstat(outFd).st_size; - final long newBytes = (mInfoDelta.mCurrentBytes + len) - curSize; - - StorageUtils.ensureAvailableSpace(mContext, outFd, newBytes); - } - out.write(buffer, 0, len); mMadeProgress = true; @@ -665,8 +648,6 @@ public class DownloadThread extends Thread { updateProgress(outFd); - } catch (ErrnoException e) { - throw new StopRequestException(STATUS_FILE_ERROR, e); } catch (IOException e) { throw new StopRequestException(STATUS_FILE_ERROR, e); } @@ -674,7 +655,8 @@ public class DownloadThread extends Thread { // Finished without error; verify length if known if (mInfoDelta.mTotalBytes != -1 && mInfoDelta.mCurrentBytes != mInfoDelta.mTotalBytes) { - throw new StopRequestException(STATUS_HTTP_DATA_ERROR, "Content length mismatch"); + throw new StopRequestException(STATUS_HTTP_DATA_ERROR, "Content length mismatch; found " + + mInfoDelta.mCurrentBytes + " instead of " + mInfoDelta.mTotalBytes); } } @@ -743,7 +725,8 @@ public class DownloadThread extends Thread { if (info.isRoaming() && !mInfo.isRoamingAllowed()) { throw new StopRequestException(STATUS_WAITING_FOR_NETWORK, "Network is roaming"); } - if (info.isMetered() && !mInfo.isMeteredAllowed(mInfoDelta.mTotalBytes)) { + if (mSystemFacade.isActiveNetworkMeteredForUid(mInfo.mUid) + && !mInfo.isMeteredAllowed(mInfoDelta.mTotalBytes)) { throw new StopRequestException(STATUS_WAITING_FOR_NETWORK, "Network is metered"); } } diff --git a/src/com/android/providers/downloads/Helpers.java b/src/com/android/providers/downloads/Helpers.java index fe81aaca..984ca79a 100644 --- a/src/com/android/providers/downloads/Helpers.java +++ b/src/com/android/providers/downloads/Helpers.java @@ -556,14 +556,6 @@ public class Helpers { return context.getCacheDir(); } - case Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION: - if (running) { - return new File(Environment.getDownloadCacheDirectory(), - Constants.DIRECTORY_CACHE_RUNNING); - } else { - return Environment.getDownloadCacheDirectory(); - } - case Downloads.Impl.DESTINATION_EXTERNAL: final File target = new File( Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS); diff --git a/src/com/android/providers/downloads/RealSystemFacade.java b/src/com/android/providers/downloads/RealSystemFacade.java index df1d245f..2d9b3a30 100644 --- a/src/com/android/providers/downloads/RealSystemFacade.java +++ b/src/com/android/providers/downloads/RealSystemFacade.java @@ -25,6 +25,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.net.ConnectivityManager; import android.net.Network; +import android.net.NetworkCapabilities; import android.net.NetworkInfo; import android.security.NetworkSecurityPolicy; import android.security.net.config.ApplicationConfig; @@ -61,6 +62,18 @@ class RealSystemFacade implements SystemFacade { } @Override + public boolean isNetworkMetered(Network network) { + return !mContext.getSystemService(ConnectivityManager.class).getNetworkCapabilities(network) + .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED); + } + + @Override + public boolean isActiveNetworkMeteredForUid(int uid) { + return mContext.getSystemService(ConnectivityManager.class) + .isActiveNetworkMeteredForUid(uid); + } + + @Override public long getMaxBytesOverMobile() { final Long value = DownloadManager.getMaxBytesOverMobile(mContext); return (value == null) ? Long.MAX_VALUE : value; diff --git a/src/com/android/providers/downloads/StorageUtils.java b/src/com/android/providers/downloads/StorageUtils.java index d7a5c33b..4d332816 100644 --- a/src/com/android/providers/downloads/StorageUtils.java +++ b/src/com/android/providers/downloads/StorageUtils.java @@ -16,185 +16,24 @@ package com.android.providers.downloads; -import static android.net.TrafficStats.MB_IN_BYTES; -import static android.provider.Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR; -import static android.text.format.DateUtils.DAY_IN_MILLIS; -import static com.android.providers.downloads.Constants.TAG; - import android.app.DownloadManager; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.Context; -import android.content.pm.IPackageDataObserver; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.os.Environment; -import android.provider.Downloads; import android.system.ErrnoException; import android.system.Os; import android.system.StructStat; -import android.system.StructStatVfs; -import android.text.TextUtils; -import android.util.Slog; -import com.android.internal.annotations.VisibleForTesting; import com.google.android.collect.Lists; -import com.google.android.collect.Sets; - -import libcore.io.IoUtils; import java.io.File; -import java.io.FileDescriptor; -import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Objects; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; /** * Utility methods for managing storage space related to * {@link DownloadManager}. */ public class StorageUtils { - - /** - * Minimum age for a file to be considered for deletion. - */ - static final long MIN_DELETE_AGE = DAY_IN_MILLIS; - - /** - * Reserved disk space to avoid filling disk. - */ - static final long RESERVED_BYTES = 32 * MB_IN_BYTES; - - @VisibleForTesting - static boolean sForceFullEviction = false; - - /** - * Ensure that requested free space exists on the partition backing the - * given {@link FileDescriptor}. If not enough space is available, it tries - * freeing up space as follows: - * <ul> - * <li>If backed by the data partition (including emulated external - * storage), then ask {@link PackageManager} to free space from cache - * directories. - * <li>If backed by the cache partition, then try deleting older downloads - * to free space. - * </ul> - */ - public static void ensureAvailableSpace(Context context, FileDescriptor fd, long bytes) - throws IOException, StopRequestException { - - long availBytes = getAvailableBytes(fd); - if (availBytes >= bytes) { - // Underlying partition has enough space; go ahead - return; - } - - // Not enough space, let's try freeing some up. Start by tracking down - // the backing partition. - final long dev; - try { - dev = Os.fstat(fd).st_dev; - } catch (ErrnoException e) { - throw e.rethrowAsIOException(); - } - - // TODO: teach about evicting caches on adopted secondary storage devices - final long dataDev = getDeviceId(Environment.getDataDirectory()); - final long cacheDev = getDeviceId(Environment.getDownloadCacheDirectory()); - final long externalDev = getDeviceId(Environment.getExternalStorageDirectory()); - - if (dev == dataDev || (dev == externalDev && Environment.isExternalStorageEmulated())) { - // File lives on internal storage; ask PackageManager to try freeing - // up space from cache directories. - final PackageManager pm = context.getPackageManager(); - final ObserverLatch observer = new ObserverLatch(); - pm.freeStorageAndNotify(sForceFullEviction ? Long.MAX_VALUE : bytes, observer); - - try { - if (!observer.latch.await(30, TimeUnit.SECONDS)) { - throw new IOException("Timeout while freeing disk space"); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - } else if (dev == cacheDev) { - // Try removing old files on cache partition - freeCacheStorage(bytes); - } - - // Did we free enough space? - availBytes = getAvailableBytes(fd); - if (availBytes < bytes) { - throw new StopRequestException(STATUS_INSUFFICIENT_SPACE_ERROR, - "Not enough free space; " + bytes + " requested, " + availBytes + " available"); - } - } - - /** - * Free requested space on cache partition, deleting oldest files first. - * We're only focused on freeing up disk space, and rely on the next orphan - * pass to clean up database entries. - */ - private static void freeCacheStorage(long bytes) { - // Only consider finished downloads - final List<ConcreteFile> files = listFilesRecursive( - Environment.getDownloadCacheDirectory(), Constants.DIRECTORY_CACHE_RUNNING, - android.os.Process.myUid()); - - Slog.d(TAG, "Found " + files.size() + " downloads on cache"); - - Collections.sort(files, new Comparator<ConcreteFile>() { - @Override - public int compare(ConcreteFile lhs, ConcreteFile rhs) { - return Long.compare(lhs.file.lastModified(), rhs.file.lastModified()); - } - }); - - final long now = System.currentTimeMillis(); - for (ConcreteFile file : files) { - if (bytes <= 0) break; - - if (now - file.file.lastModified() < MIN_DELETE_AGE) { - Slog.d(TAG, "Skipping recently modified " + file.file); - } else { - final long len = file.file.length(); - Slog.d(TAG, "Deleting " + file.file + " to reclaim " + len); - bytes -= len; - file.file.delete(); - } - } - } - - /** - * Return number of available bytes on the filesystem backing the given - * {@link FileDescriptor}, minus any {@link #RESERVED_BYTES} buffer. - */ - private static long getAvailableBytes(FileDescriptor fd) throws IOException { - try { - final StructStatVfs stat = Os.fstatvfs(fd); - return (stat.f_bavail * stat.f_bsize) - RESERVED_BYTES; - } catch (ErrnoException e) { - throw e.rethrowAsIOException(); - } - } - - private static long getDeviceId(File file) { - try { - return Os.stat(file.getAbsolutePath()).st_dev; - } catch (ErrnoException e) { - // Safe since dev_t is uint - return -1; - } - } - /** * Return list of all normal files under the given directory, traversing * directories recursively. @@ -260,13 +99,4 @@ public class StorageUtils { return false; } } - - static class ObserverLatch extends IPackageDataObserver.Stub { - public final CountDownLatch latch = new CountDownLatch(1); - - @Override - public void onRemoveCompleted(String packageName, boolean succeeded) { - latch.countDown(); - } - } } diff --git a/src/com/android/providers/downloads/SystemFacade.java b/src/com/android/providers/downloads/SystemFacade.java index c34317cb..dec0cb28 100644 --- a/src/com/android/providers/downloads/SystemFacade.java +++ b/src/com/android/providers/downloads/SystemFacade.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager.NameNotFoundException; import android.net.Network; +import android.net.NetworkCapabilities; import android.net.NetworkInfo; import java.security.GeneralSecurityException; @@ -35,6 +36,10 @@ interface SystemFacade { public NetworkInfo getNetworkInfo(Network network, int uid, boolean ignoreBlocked); + public boolean isNetworkMetered(Network network); + + public boolean isActiveNetworkMeteredForUid(int uid); + /** * @return maximum size, in bytes, of downloads that may go over a mobile connection; or null if * there's no limit |