| /* |
| * 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()); |
| } |
| } |
| } |