summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2017-07-15 12:23:05 -0600
committerJeff Sharkey <jsharkey@android.com>2017-07-15 15:04:30 -0600
commitd635ac295850ba23d528c02b1e5c6eb44b64b22b (patch)
tree4c371e93cf162adffcb891752bdeb3f902252d18
parentc442fbd95ad5b3de9e58a11e431f419edb4fe978 (diff)
downloadandroid_packages_providers_DownloadProvider-d635ac295850ba23d528c02b1e5c6eb44b64b22b.tar.gz
android_packages_providers_DownloadProvider-d635ac295850ba23d528c02b1e5c6eb44b64b22b.tar.bz2
android_packages_providers_DownloadProvider-d635ac295850ba23d528c02b1e5c6eb44b64b22b.zip
Allocate space using new StorageManager API.
Instead of reaching directly into PackageManager, use the new StorageManager API to allocate disk space for downloads. This wraps both clearing cached files and fallocate() into a single method. Remove support for storing downloads on the /cache partition, which doesn't exist on many devices. Bug: 63057877 Test: bit DownloadProviderTests:* Exempt-From-Owner-Approval: Bug 63673347 Change-Id: I5749f7a2f7ded9157fea763dc652bf4da88d86ff
-rw-r--r--src/com/android/providers/downloads/DownloadInfo.java7
-rw-r--r--src/com/android/providers/downloads/DownloadNotifier.java6
-rw-r--r--src/com/android/providers/downloads/DownloadProvider.java11
-rw-r--r--src/com/android/providers/downloads/DownloadThread.java47
-rw-r--r--src/com/android/providers/downloads/Helpers.java8
-rw-r--r--src/com/android/providers/downloads/StorageUtils.java170
-rw-r--r--tests/AndroidManifest.xml2
-rw-r--r--tests/src/com/android/providers/downloads/AbstractDownloadProviderFunctionalTest.java35
-rw-r--r--tests/src/com/android/providers/downloads/StorageTest.java249
9 files changed, 32 insertions, 503 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 c36dbd8c..71807640 100644
--- a/src/com/android/providers/downloads/DownloadNotifier.java
+++ b/src/com/android/providers/downloads/DownloadNotifier.java
@@ -264,11 +264,7 @@ 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);
diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java
index d50b394c..d47010dd 100644
--- a/src/com/android/providers/downloads/DownloadProvider.java
+++ b/src/com/android/providers/downloads/DownloadProvider.java
@@ -580,8 +580,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");
}
@@ -608,12 +607,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);
}
@@ -1306,6 +1299,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/DownloadThread.java b/src/com/android/providers/downloads/DownloadThread.java
index b9849569..9c920053 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;
@@ -63,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;
@@ -116,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;
@@ -244,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;
@@ -563,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);
@@ -649,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;
@@ -664,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);
}
@@ -673,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);
}
}
diff --git a/src/com/android/providers/downloads/Helpers.java b/src/com/android/providers/downloads/Helpers.java
index 7354076b..2b55eb87 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/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/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index ec73ca2e..0f4166f5 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -21,7 +21,7 @@
<uses-permission android:name="android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED" />
- <application>
+ <application android:usesCleartextTraffic="true">
<uses-library android:name="android.test.runner" />
</application>
diff --git a/tests/src/com/android/providers/downloads/AbstractDownloadProviderFunctionalTest.java b/tests/src/com/android/providers/downloads/AbstractDownloadProviderFunctionalTest.java
index 813252a8..13d2c36e 100644
--- a/tests/src/com/android/providers/downloads/AbstractDownloadProviderFunctionalTest.java
+++ b/tests/src/com/android/providers/downloads/AbstractDownloadProviderFunctionalTest.java
@@ -171,8 +171,8 @@ public abstract class AbstractDownloadProviderFunctionalTest extends
Helpers.setSystemFacade(mSystemFacade);
mSystemFacade.setUp();
- assertTrue(isDatabaseEmpty()); // ensure we're not messing with real data
- assertTrue(isDatabaseSecureAgainstBadSelection());
+ assertDatabaseEmpty(); // ensure we're not messing with real data
+ assertDatabaseSecureAgainstBadSelection();
mServer = new MockWebServer();
mServer.play();
}
@@ -188,34 +188,23 @@ public abstract class AbstractDownloadProviderFunctionalTest extends
protected void startDownload(long id) {
final JobParameters params = mock(JobParameters.class);
when(params.getJobId()).thenReturn((int) id);
+ getService().onBind(null);
getService().onStartJob(params);
}
- private boolean isDatabaseEmpty() {
- Cursor cursor = mResolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
- null, null, null, null);
- try {
- return cursor.getCount() == 0;
- } finally {
- cursor.close();
+ private void assertDatabaseEmpty() {
+ try (Cursor cursor = mResolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
+ null, null, null, null)) {
+ assertEquals(0, cursor.getCount());
}
}
- private boolean isDatabaseSecureAgainstBadSelection() {
- Cursor cursor = null;
- try {
- cursor = mResolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, null,
- "('1'='1'))) ORDER BY lastmod DESC--", null, null);
+ private void assertDatabaseSecureAgainstBadSelection() {
+ try (Cursor cursor = mResolver.query(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, null,
+ "('1'='1'))) ORDER BY lastmod DESC--", null, null)) {
+ fail("Database isn't secure!");
+ } catch (Exception expected) {
}
- catch (Exception e) {
- return true;
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
-
- return false;
}
/**
diff --git a/tests/src/com/android/providers/downloads/StorageTest.java b/tests/src/com/android/providers/downloads/StorageTest.java
deleted file mode 100644
index 95bd3676..00000000
--- a/tests/src/com/android/providers/downloads/StorageTest.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.providers.downloads;
-
-import static android.app.DownloadManager.COLUMN_REASON;
-import static android.app.DownloadManager.ERROR_INSUFFICIENT_SPACE;
-import static android.app.DownloadManager.STATUS_FAILED;
-import static android.app.DownloadManager.STATUS_SUCCESSFUL;
-import static android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION;
-import static android.provider.Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION;
-
-import android.app.DownloadManager;
-import android.content.pm.PackageManager;
-import android.os.Environment;
-import android.os.StatFs;
-import android.provider.Downloads.Impl;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.StructStatVfs;
-import android.test.MoreAsserts;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.util.Log;
-
-import com.android.providers.downloads.StorageUtils.ObserverLatch;
-import com.google.mockwebserver.MockResponse;
-import com.google.mockwebserver.SocketPolicy;
-
-import libcore.io.ForwardingOs;
-import libcore.io.IoUtils;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.concurrent.TimeUnit;
-
-@MediumTest
-public class StorageTest extends AbstractPublicApiTest {
- private static final String TAG = "StorageTest";
-
- private static final int DOWNLOAD_SIZE = 512 * 1024;
- private static final byte[] DOWNLOAD_BODY;
-
- static {
- DOWNLOAD_BODY = new byte[DOWNLOAD_SIZE];
- for (int i = 0; i < DOWNLOAD_SIZE; i++) {
- DOWNLOAD_BODY[i] = (byte) (i % 32);
- }
- }
-
- private libcore.io.Os mOriginal;
- private long mStealBytes;
-
- public StorageTest() {
- super(new FakeSystemFacade());
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- StorageUtils.sForceFullEviction = true;
- mStealBytes = 0;
-
- mOriginal = libcore.io.Libcore.os;
- libcore.io.Libcore.os = new ForwardingOs(mOriginal) {
- @Override
- public StructStatVfs statvfs(String path) throws ErrnoException {
- return stealBytes(os.statvfs(path));
- }
-
- @Override
- public StructStatVfs fstatvfs(FileDescriptor fd) throws ErrnoException {
- return stealBytes(os.fstatvfs(fd));
- }
-
- private StructStatVfs stealBytes(StructStatVfs s) {
- final long stealBlocks = (mStealBytes + (s.f_bsize - 1)) / s.f_bsize;
- final long f_bavail = s.f_bavail - stealBlocks;
- return new StructStatVfs(s.f_bsize, s.f_frsize, s.f_blocks, s.f_bfree, f_bavail,
- s.f_files, s.f_ffree, s.f_favail, s.f_fsid, s.f_flag, s.f_namemax);
- }
- };
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
-
- StorageUtils.sForceFullEviction = false;
- mStealBytes = 0;
-
- if (mOriginal != null) {
- libcore.io.Libcore.os = mOriginal;
- }
- }
-
- private enum CacheStatus { CLEAN, DIRTY }
- private enum BodyType { COMPLETE, CHUNKED }
-
- public void testDataDirtyComplete() throws Exception {
- prepareAndRunDownload(DESTINATION_CACHE_PARTITION,
- CacheStatus.DIRTY, BodyType.COMPLETE,
- STATUS_SUCCESSFUL, -1);
- }
-
- public void testDataDirtyChunked() throws Exception {
- prepareAndRunDownload(DESTINATION_CACHE_PARTITION,
- CacheStatus.DIRTY, BodyType.CHUNKED,
- STATUS_SUCCESSFUL, -1);
- }
-
- public void testDataCleanComplete() throws Exception {
- prepareAndRunDownload(DESTINATION_CACHE_PARTITION,
- CacheStatus.CLEAN, BodyType.COMPLETE,
- STATUS_FAILED, ERROR_INSUFFICIENT_SPACE);
- }
-
- public void testDataCleanChunked() throws Exception {
- prepareAndRunDownload(DESTINATION_CACHE_PARTITION,
- CacheStatus.CLEAN, BodyType.CHUNKED,
- STATUS_FAILED, ERROR_INSUFFICIENT_SPACE);
- }
-
- public void testCacheDirtyComplete() throws Exception {
- prepareAndRunDownload(DESTINATION_SYSTEMCACHE_PARTITION,
- CacheStatus.DIRTY, BodyType.COMPLETE,
- STATUS_SUCCESSFUL, -1);
- }
-
- public void testCacheDirtyChunked() throws Exception {
- prepareAndRunDownload(DESTINATION_SYSTEMCACHE_PARTITION,
- CacheStatus.DIRTY, BodyType.CHUNKED,
- STATUS_SUCCESSFUL, -1);
- }
-
- public void testCacheCleanComplete() throws Exception {
- prepareAndRunDownload(DESTINATION_SYSTEMCACHE_PARTITION,
- CacheStatus.CLEAN, BodyType.COMPLETE,
- STATUS_FAILED, ERROR_INSUFFICIENT_SPACE);
- }
-
- public void testCacheCleanChunked() throws Exception {
- prepareAndRunDownload(DESTINATION_SYSTEMCACHE_PARTITION,
- CacheStatus.CLEAN, BodyType.CHUNKED,
- STATUS_FAILED, ERROR_INSUFFICIENT_SPACE);
- }
-
- private void prepareAndRunDownload(
- int dest, CacheStatus cache, BodyType body, int expectedStatus, int expectedReason)
- throws Exception {
-
- // Ensure that we've purged everything possible for destination
- final File dirtyDir;
- if (dest == DESTINATION_CACHE_PARTITION) {
- final PackageManager pm = getContext().getPackageManager();
- final ObserverLatch observer = new ObserverLatch();
- pm.freeStorageAndNotify(Long.MAX_VALUE, observer);
-
- try {
- if (!observer.latch.await(30, TimeUnit.SECONDS)) {
- throw new IOException("Timeout while freeing disk space");
- }
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- }
-
- dirtyDir = getContext().getCacheDir();
-
- } else if (dest == DESTINATION_SYSTEMCACHE_PARTITION) {
- IoUtils.deleteContents(Environment.getDownloadCacheDirectory());
- dirtyDir = Environment.getDownloadCacheDirectory();
-
- } else {
- throw new IllegalArgumentException("Unknown destination");
- }
-
- // Allocate a cache file, if requested, making it large enough and old
- // enough to clear.
- final File dirtyFile;
- if (cache == CacheStatus.DIRTY) {
- dirtyFile = new File(dirtyDir, "cache_file.bin");
- assertTrue(dirtyFile.createNewFile());
- final FileOutputStream os = new FileOutputStream(dirtyFile);
- final int dirtySize = (DOWNLOAD_SIZE * 3) / 2;
- Os.posix_fallocate(os.getFD(), 0, dirtySize);
- IoUtils.closeQuietly(os);
-
- dirtyFile.setLastModified(
- System.currentTimeMillis() - (StorageUtils.MIN_DELETE_AGE * 2));
- } else {
- dirtyFile = null;
- }
-
- // At this point, hide all other disk space to make the download fail;
- // if we have a dirty cache file it can be cleared to let us proceed.
- final long targetFree = StorageUtils.RESERVED_BYTES + (DOWNLOAD_SIZE / 2);
-
- final StatFs stat = new StatFs(dirtyDir.getAbsolutePath());
- Log.d(TAG, "Available bytes (before steal): " + stat.getAvailableBytes());
- mStealBytes = stat.getAvailableBytes() - targetFree;
-
- stat.restat(dirtyDir.getAbsolutePath());
- Log.d(TAG, "Available bytes (after steal): " + stat.getAvailableBytes());
-
- final MockResponse resp = new MockResponse().setResponseCode(200)
- .setHeader("Content-type", "text/plain")
- .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END);
- if (body == BodyType.CHUNKED) {
- resp.setChunkedBody(DOWNLOAD_BODY, 1021);
- } else {
- resp.setBody(DOWNLOAD_BODY);
- }
- enqueueResponse(resp);
-
- final DownloadManager.Request req = getRequest();
- if (dest == Impl.DESTINATION_SYSTEMCACHE_PARTITION) {
- req.setDestinationToSystemCache();
- }
- final Download download = enqueueRequest(req);
- download.runUntilStatus(expectedStatus);
-
- if (expectedStatus == STATUS_SUCCESSFUL) {
- MoreAsserts.assertEquals(DOWNLOAD_BODY, download.getRawContents());
- }
-
- if (expectedReason != -1) {
- assertEquals(expectedReason, download.getLongField(COLUMN_REASON));
- }
-
- if (dirtyFile != null) {
- assertFalse(dirtyFile.exists());
- }
- }
-}