• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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