1 /* 2 * Copyright (C) 2014 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.app.DownloadManager.COLUMN_REASON; 20 import static android.app.DownloadManager.ERROR_INSUFFICIENT_SPACE; 21 import static android.app.DownloadManager.STATUS_FAILED; 22 import static android.app.DownloadManager.STATUS_SUCCESSFUL; 23 import static android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION; 24 import static android.provider.Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION; 25 26 import android.app.DownloadManager; 27 import android.content.pm.PackageManager; 28 import android.os.Environment; 29 import android.os.StatFs; 30 import android.provider.Downloads.Impl; 31 import android.system.ErrnoException; 32 import android.system.Os; 33 import android.system.StructStatVfs; 34 import android.test.MoreAsserts; 35 import android.test.suitebuilder.annotation.MediumTest; 36 import android.util.Log; 37 38 import com.android.providers.downloads.StorageUtils.ObserverLatch; 39 import com.google.mockwebserver.MockResponse; 40 import com.google.mockwebserver.SocketPolicy; 41 42 import libcore.io.ForwardingOs; 43 import libcore.io.IoUtils; 44 45 import java.io.File; 46 import java.io.FileDescriptor; 47 import java.io.FileOutputStream; 48 import java.io.IOException; 49 import java.util.concurrent.TimeUnit; 50 51 @MediumTest 52 public class StorageTest extends AbstractPublicApiTest { 53 private static final String TAG = "StorageTest"; 54 55 private static final int DOWNLOAD_SIZE = 512 * 1024; 56 private static final byte[] DOWNLOAD_BODY; 57 58 static { 59 DOWNLOAD_BODY = new byte[DOWNLOAD_SIZE]; 60 for (int i = 0; i < DOWNLOAD_SIZE; i++) { 61 DOWNLOAD_BODY[i] = (byte) (i % 32); 62 } 63 } 64 65 private libcore.io.Os mOriginal; 66 private long mStealBytes; 67 StorageTest()68 public StorageTest() { 69 super(new FakeSystemFacade()); 70 } 71 72 @Override setUp()73 protected void setUp() throws Exception { 74 super.setUp(); 75 76 StorageUtils.sForceFullEviction = true; 77 mStealBytes = 0; 78 79 mOriginal = libcore.io.Libcore.os; 80 libcore.io.Libcore.os = new ForwardingOs(mOriginal) { 81 @Override 82 public StructStatVfs statvfs(String path) throws ErrnoException { 83 return stealBytes(os.statvfs(path)); 84 } 85 86 @Override 87 public StructStatVfs fstatvfs(FileDescriptor fd) throws ErrnoException { 88 return stealBytes(os.fstatvfs(fd)); 89 } 90 91 private StructStatVfs stealBytes(StructStatVfs s) { 92 final long stealBlocks = (mStealBytes + (s.f_bsize - 1)) / s.f_bsize; 93 final long f_bavail = s.f_bavail - stealBlocks; 94 return new StructStatVfs(s.f_bsize, s.f_frsize, s.f_blocks, s.f_bfree, f_bavail, 95 s.f_files, s.f_ffree, s.f_favail, s.f_fsid, s.f_flag, s.f_namemax); 96 } 97 }; 98 } 99 100 @Override tearDown()101 protected void tearDown() throws Exception { 102 super.tearDown(); 103 104 StorageUtils.sForceFullEviction = false; 105 mStealBytes = 0; 106 107 if (mOriginal != null) { 108 libcore.io.Libcore.os = mOriginal; 109 } 110 } 111 112 private enum CacheStatus { CLEAN, DIRTY } 113 private enum BodyType { COMPLETE, CHUNKED } 114 testDataDirtyComplete()115 public void testDataDirtyComplete() throws Exception { 116 prepareAndRunDownload(DESTINATION_CACHE_PARTITION, 117 CacheStatus.DIRTY, BodyType.COMPLETE, 118 STATUS_SUCCESSFUL, -1); 119 } 120 testDataDirtyChunked()121 public void testDataDirtyChunked() throws Exception { 122 prepareAndRunDownload(DESTINATION_CACHE_PARTITION, 123 CacheStatus.DIRTY, BodyType.CHUNKED, 124 STATUS_SUCCESSFUL, -1); 125 } 126 testDataCleanComplete()127 public void testDataCleanComplete() throws Exception { 128 prepareAndRunDownload(DESTINATION_CACHE_PARTITION, 129 CacheStatus.CLEAN, BodyType.COMPLETE, 130 STATUS_FAILED, ERROR_INSUFFICIENT_SPACE); 131 } 132 testDataCleanChunked()133 public void testDataCleanChunked() throws Exception { 134 prepareAndRunDownload(DESTINATION_CACHE_PARTITION, 135 CacheStatus.CLEAN, BodyType.CHUNKED, 136 STATUS_FAILED, ERROR_INSUFFICIENT_SPACE); 137 } 138 testCacheDirtyComplete()139 public void testCacheDirtyComplete() throws Exception { 140 prepareAndRunDownload(DESTINATION_SYSTEMCACHE_PARTITION, 141 CacheStatus.DIRTY, BodyType.COMPLETE, 142 STATUS_SUCCESSFUL, -1); 143 } 144 testCacheDirtyChunked()145 public void testCacheDirtyChunked() throws Exception { 146 prepareAndRunDownload(DESTINATION_SYSTEMCACHE_PARTITION, 147 CacheStatus.DIRTY, BodyType.CHUNKED, 148 STATUS_SUCCESSFUL, -1); 149 } 150 testCacheCleanComplete()151 public void testCacheCleanComplete() throws Exception { 152 prepareAndRunDownload(DESTINATION_SYSTEMCACHE_PARTITION, 153 CacheStatus.CLEAN, BodyType.COMPLETE, 154 STATUS_FAILED, ERROR_INSUFFICIENT_SPACE); 155 } 156 testCacheCleanChunked()157 public void testCacheCleanChunked() throws Exception { 158 prepareAndRunDownload(DESTINATION_SYSTEMCACHE_PARTITION, 159 CacheStatus.CLEAN, BodyType.CHUNKED, 160 STATUS_FAILED, ERROR_INSUFFICIENT_SPACE); 161 } 162 prepareAndRunDownload( int dest, CacheStatus cache, BodyType body, int expectedStatus, int expectedReason)163 private void prepareAndRunDownload( 164 int dest, CacheStatus cache, BodyType body, int expectedStatus, int expectedReason) 165 throws Exception { 166 167 // Ensure that we've purged everything possible for destination 168 final File dirtyDir; 169 if (dest == DESTINATION_CACHE_PARTITION) { 170 final PackageManager pm = getContext().getPackageManager(); 171 final ObserverLatch observer = new ObserverLatch(); 172 pm.freeStorageAndNotify(Long.MAX_VALUE, observer); 173 174 try { 175 if (!observer.latch.await(30, TimeUnit.SECONDS)) { 176 throw new IOException("Timeout while freeing disk space"); 177 } 178 } catch (InterruptedException e) { 179 Thread.currentThread().interrupt(); 180 } 181 182 dirtyDir = getContext().getCacheDir(); 183 184 } else if (dest == DESTINATION_SYSTEMCACHE_PARTITION) { 185 IoUtils.deleteContents(Environment.getDownloadCacheDirectory()); 186 dirtyDir = Environment.getDownloadCacheDirectory(); 187 188 } else { 189 throw new IllegalArgumentException("Unknown destination"); 190 } 191 192 // Allocate a cache file, if requested, making it large enough and old 193 // enough to clear. 194 final File dirtyFile; 195 if (cache == CacheStatus.DIRTY) { 196 dirtyFile = new File(dirtyDir, "cache_file.bin"); 197 assertTrue(dirtyFile.createNewFile()); 198 final FileOutputStream os = new FileOutputStream(dirtyFile); 199 final int dirtySize = (DOWNLOAD_SIZE * 3) / 2; 200 Os.posix_fallocate(os.getFD(), 0, dirtySize); 201 IoUtils.closeQuietly(os); 202 203 dirtyFile.setLastModified( 204 System.currentTimeMillis() - (StorageUtils.MIN_DELETE_AGE * 2)); 205 } else { 206 dirtyFile = null; 207 } 208 209 // At this point, hide all other disk space to make the download fail; 210 // if we have a dirty cache file it can be cleared to let us proceed. 211 final long targetFree = StorageUtils.RESERVED_BYTES + (DOWNLOAD_SIZE / 2); 212 213 final StatFs stat = new StatFs(dirtyDir.getAbsolutePath()); 214 Log.d(TAG, "Available bytes (before steal): " + stat.getAvailableBytes()); 215 mStealBytes = stat.getAvailableBytes() - targetFree; 216 217 stat.restat(dirtyDir.getAbsolutePath()); 218 Log.d(TAG, "Available bytes (after steal): " + stat.getAvailableBytes()); 219 220 final MockResponse resp = new MockResponse().setResponseCode(200) 221 .setHeader("Content-type", "text/plain") 222 .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END); 223 if (body == BodyType.CHUNKED) { 224 resp.setChunkedBody(DOWNLOAD_BODY, 1021); 225 } else { 226 resp.setBody(DOWNLOAD_BODY); 227 } 228 enqueueResponse(resp); 229 230 final DownloadManager.Request req = getRequest(); 231 if (dest == Impl.DESTINATION_SYSTEMCACHE_PARTITION) { 232 req.setDestinationToSystemCache(); 233 } 234 final Download download = enqueueRequest(req); 235 download.runUntilStatus(expectedStatus); 236 237 if (expectedStatus == STATUS_SUCCESSFUL) { 238 MoreAsserts.assertEquals(DOWNLOAD_BODY, download.getRawContents()); 239 } 240 241 if (expectedReason != -1) { 242 assertEquals(expectedReason, download.getLongField(COLUMN_REASON)); 243 } 244 245 if (dirtyFile != null) { 246 assertFalse(dirtyFile.exists()); 247 } 248 } 249 } 250