1 /* 2 * Copyright (C) 2017 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.cts.storagestatsapp; 18 19 import static android.os.storage.StorageManager.UUID_DEFAULT; 20 21 import static com.android.cts.storageapp.Utils.CACHE_ALL; 22 import static com.android.cts.storageapp.Utils.CODE_ALL; 23 import static com.android.cts.storageapp.Utils.DATA_ALL; 24 import static com.android.cts.storageapp.Utils.MB_IN_BYTES; 25 import static com.android.cts.storageapp.Utils.PKG_A; 26 import static com.android.cts.storageapp.Utils.PKG_B; 27 import static com.android.cts.storageapp.Utils.TAG; 28 import static com.android.cts.storageapp.Utils.assertAtLeast; 29 import static com.android.cts.storageapp.Utils.assertMostlyEquals; 30 import static com.android.cts.storageapp.Utils.getSizeManual; 31 import static com.android.cts.storageapp.Utils.logCommand; 32 import static com.android.cts.storageapp.Utils.makeUniqueFile; 33 import static com.android.cts.storageapp.Utils.useFallocate; 34 import static com.android.cts.storageapp.Utils.useSpace; 35 import static com.android.cts.storageapp.Utils.useWrite; 36 37 import android.app.Activity; 38 import android.app.usage.ExternalStorageStats; 39 import android.app.usage.StorageStats; 40 import android.app.usage.StorageStatsManager; 41 import android.content.BroadcastReceiver; 42 import android.content.ComponentName; 43 import android.content.ContentProviderClient; 44 import android.content.Context; 45 import android.content.Intent; 46 import android.content.pm.ApplicationInfo; 47 import android.content.pm.PackageManager; 48 import android.os.Build; 49 import android.os.Bundle; 50 import android.os.Environment; 51 import android.os.UserHandle; 52 import android.os.storage.StorageManager; 53 import android.support.test.uiautomator.UiDevice; 54 import android.test.InstrumentationTestCase; 55 import android.util.Log; 56 import android.util.MutableLong; 57 58 import com.android.cts.storageapp.UtilsReceiver; 59 60 import junit.framework.AssertionFailedError; 61 62 import java.io.File; 63 import java.util.UUID; 64 import java.util.concurrent.CountDownLatch; 65 import java.util.concurrent.TimeUnit; 66 67 /** 68 * Tests to verify {@link StorageStatsManager} behavior. 69 */ 70 public class StorageStatsTest extends InstrumentationTestCase { 71 getContext()72 private Context getContext() { 73 return getInstrumentation().getContext(); 74 } 75 76 /** 77 * Require that quota support be fully enabled on devices that first ship 78 * with P. This test verifies that both kernel options and the fstab 'quota' 79 * option are enabled. 80 */ testVerify()81 public void testVerify() throws Exception { 82 if (Build.VERSION.FIRST_SDK_INT >= Build.VERSION_CODES.P) { 83 final StorageStatsManager stats = getContext() 84 .getSystemService(StorageStatsManager.class); 85 assertTrue("Devices that first ship with P or newer must enable quotas to " 86 + "support StorageStatsManager APIs. You may need to enable the " 87 + "CONFIG_QUOTA, CONFIG_QFMT_V2, CONFIG_QUOTACTL kernel options " 88 + "and add the 'quota' fstab option on /data.", 89 stats.isQuotaSupported(UUID_DEFAULT)); 90 assertTrue("Devices that first ship with P or newer must enable resgid to " 91 + "preserve system stability in the face of abusive apps.", 92 stats.isReservedSupported(UUID_DEFAULT)); 93 } 94 } 95 testVerifySummary()96 public void testVerifySummary() throws Exception { 97 final StorageStatsManager stats = getContext().getSystemService(StorageStatsManager.class); 98 99 final long actualTotal = stats.getTotalBytes(UUID_DEFAULT); 100 final long expectedTotal = Environment.getDataDirectory().getTotalSpace(); 101 assertAtLeast(expectedTotal, actualTotal); 102 103 final long actualFree = stats.getFreeBytes(UUID_DEFAULT); 104 final long expectedFree = Environment.getDataDirectory().getUsableSpace(); 105 assertAtLeast(expectedFree, actualFree); 106 } 107 testVerifyStats()108 public void testVerifyStats() throws Exception { 109 final StorageStatsManager stats = getContext().getSystemService(StorageStatsManager.class); 110 final int uid = android.os.Process.myUid(); 111 final UserHandle user = UserHandle.getUserHandleForUid(uid); 112 113 final StorageStats beforeApp = stats.queryStatsForUid(UUID_DEFAULT, uid); 114 final StorageStats beforeUser = stats.queryStatsForUser(UUID_DEFAULT, user); 115 116 useSpace(getContext()); 117 118 final StorageStats afterApp = stats.queryStatsForUid(UUID_DEFAULT, uid); 119 final StorageStats afterUser = stats.queryStatsForUser(UUID_DEFAULT, user); 120 121 final long deltaCode = CODE_ALL; 122 assertMostlyEquals(deltaCode, afterApp.getAppBytes() - beforeApp.getAppBytes()); 123 assertMostlyEquals(deltaCode, afterUser.getAppBytes() - beforeUser.getAppBytes()); 124 125 final long deltaData = DATA_ALL; 126 assertMostlyEquals(deltaData, afterApp.getDataBytes() - beforeApp.getDataBytes()); 127 assertMostlyEquals(deltaData, afterUser.getDataBytes() - beforeUser.getDataBytes()); 128 129 final long deltaCache = CACHE_ALL; 130 assertMostlyEquals(deltaCache, afterApp.getCacheBytes() - beforeApp.getCacheBytes()); 131 assertMostlyEquals(deltaCache, afterUser.getCacheBytes() - beforeUser.getCacheBytes()); 132 } 133 testVerifyStatsMultiple()134 public void testVerifyStatsMultiple() throws Exception { 135 final PackageManager pm = getContext().getPackageManager(); 136 final StorageStatsManager stats = getContext().getSystemService(StorageStatsManager.class); 137 138 final ApplicationInfo a = pm.getApplicationInfo(PKG_A, 0); 139 final ApplicationInfo b = pm.getApplicationInfo(PKG_B, 0); 140 141 final StorageStats as = stats.queryStatsForUid(UUID_DEFAULT, a.uid); 142 final StorageStats bs = stats.queryStatsForUid(UUID_DEFAULT, b.uid); 143 144 assertMostlyEquals(DATA_ALL * 2, as.getDataBytes()); 145 assertMostlyEquals(CACHE_ALL * 2, as.getCacheBytes()); 146 147 assertMostlyEquals(DATA_ALL, bs.getDataBytes()); 148 assertMostlyEquals(CACHE_ALL, bs.getCacheBytes()); 149 150 // Since OBB storage space may be shared or isolated between users, 151 // we'll accept either expected or double usage. 152 try { 153 assertMostlyEquals(CODE_ALL * 2, as.getAppBytes(), 5 * MB_IN_BYTES); 154 assertMostlyEquals(CODE_ALL * 1, bs.getAppBytes(), 5 * MB_IN_BYTES); 155 } catch (AssertionFailedError e) { 156 assertMostlyEquals(CODE_ALL * 4, as.getAppBytes(), 5 * MB_IN_BYTES); 157 assertMostlyEquals(CODE_ALL * 2, bs.getAppBytes(), 5 * MB_IN_BYTES); 158 } 159 } 160 161 /** 162 * Create some external files of specific media types and ensure that 163 * they're tracked correctly. 164 */ testVerifyStatsExternal()165 public void testVerifyStatsExternal() throws Exception { 166 final StorageStatsManager stats = getContext().getSystemService(StorageStatsManager.class); 167 final int uid = android.os.Process.myUid(); 168 final UserHandle user = UserHandle.getUserHandleForUid(uid); 169 170 final ExternalStorageStats before = stats.queryExternalStatsForUser(UUID_DEFAULT, user); 171 172 final File dir = Environment.getExternalStorageDirectory(); 173 final File downloadsDir = Environment.getExternalStoragePublicDirectory( 174 Environment.DIRECTORY_DOWNLOADS); 175 downloadsDir.mkdirs(); 176 177 final File image = new File(dir, System.nanoTime() + ".jpg"); 178 final File video = new File(downloadsDir, System.nanoTime() + ".MP4"); 179 final File audio = new File(dir, System.nanoTime() + ".png.WaV"); 180 final File internal = new File( 181 getContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES), "test.jpg"); 182 183 useWrite(image, 2 * MB_IN_BYTES); 184 useWrite(video, 3 * MB_IN_BYTES); 185 useWrite(audio, 5 * MB_IN_BYTES); 186 useWrite(internal, 7 * MB_IN_BYTES); 187 188 final ExternalStorageStats afterInit = stats.queryExternalStatsForUser(UUID_DEFAULT, user); 189 190 assertMostlyEquals(17 * MB_IN_BYTES, afterInit.getTotalBytes() - before.getTotalBytes()); 191 assertMostlyEquals(5 * MB_IN_BYTES, afterInit.getAudioBytes() - before.getAudioBytes()); 192 assertMostlyEquals(3 * MB_IN_BYTES, afterInit.getVideoBytes() - before.getVideoBytes()); 193 assertMostlyEquals(2 * MB_IN_BYTES, afterInit.getImageBytes() - before.getImageBytes()); 194 assertMostlyEquals(7 * MB_IN_BYTES, afterInit.getAppBytes() - before.getAppBytes()); 195 196 // Rename to ensure that stats are updated 197 video.renameTo(new File(dir, System.nanoTime() + ".PnG")); 198 199 final ExternalStorageStats afterRename = stats.queryExternalStatsForUser(UUID_DEFAULT, user); 200 201 assertMostlyEquals(17 * MB_IN_BYTES, afterRename.getTotalBytes() - before.getTotalBytes()); 202 assertMostlyEquals(5 * MB_IN_BYTES, afterRename.getAudioBytes() - before.getAudioBytes()); 203 assertMostlyEquals(0 * MB_IN_BYTES, afterRename.getVideoBytes() - before.getVideoBytes()); 204 assertMostlyEquals(5 * MB_IN_BYTES, afterRename.getImageBytes() - before.getImageBytes()); 205 assertMostlyEquals(7 * MB_IN_BYTES, afterRename.getAppBytes() - before.getAppBytes()); 206 } 207 208 /** 209 * Measuring external storage manually should always be consistent with 210 * whatever the stats APIs are returning. 211 */ testVerifyStatsExternalConsistent()212 public void testVerifyStatsExternalConsistent() throws Exception { 213 final StorageStatsManager stats = getContext().getSystemService(StorageStatsManager.class); 214 final UserHandle user = android.os.Process.myUserHandle(); 215 216 useSpace(getContext()); 217 218 final File top = Environment.getExternalStorageDirectory(); 219 final File pics = Environment 220 .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); 221 pics.mkdirs(); 222 223 useWrite(makeUniqueFile(top), 5 * MB_IN_BYTES); 224 useWrite(makeUniqueFile(pics), 5 * MB_IN_BYTES); 225 useWrite(makeUniqueFile(pics), 5 * MB_IN_BYTES); 226 227 // for fuse file system 228 Thread.sleep(10000); 229 230 // TODO: remove this once 34723223 is fixed 231 logCommand("sync"); 232 233 final long manualSize = getSizeManual(Environment.getExternalStorageDirectory(), true); 234 final long statsSize = stats.queryExternalStatsForUser(UUID_DEFAULT, user).getTotalBytes(); 235 236 assertMostlyEquals(manualSize, statsSize); 237 } 238 testVerifyCategory()239 public void testVerifyCategory() throws Exception { 240 final PackageManager pm = getContext().getPackageManager(); 241 final ApplicationInfo a = pm.getApplicationInfo(PKG_A, 0); 242 final ApplicationInfo b = pm.getApplicationInfo(PKG_B, 0); 243 244 assertEquals(ApplicationInfo.CATEGORY_VIDEO, a.category); 245 assertEquals(ApplicationInfo.CATEGORY_UNDEFINED, b.category); 246 } 247 testCacheClearing()248 public void testCacheClearing() throws Exception { 249 final Context context = getContext(); 250 final StorageManager sm = context.getSystemService(StorageManager.class); 251 final StorageStatsManager stats = context.getSystemService(StorageStatsManager.class); 252 final UserHandle user = android.os.Process.myUserHandle(); 253 254 final File filesDir = context.getFilesDir(); 255 final UUID filesUuid = sm.getUuidForPath(filesDir); 256 final String pmUuid = filesUuid.equals(StorageManager.UUID_DEFAULT) ? "internal" 257 : filesUuid.toString(); 258 259 final long beforeAllocatable = sm.getAllocatableBytes(filesUuid); 260 final long beforeFree = stats.getFreeBytes(filesUuid); 261 final long beforeRaw = filesDir.getUsableSpace(); 262 263 Log.d(TAG, "Before raw " + beforeRaw + ", free " + beforeFree + ", allocatable " 264 + beforeAllocatable); 265 266 assertMostlyEquals(0, getCacheBytes(PKG_A, user)); 267 assertMostlyEquals(0, getCacheBytes(PKG_B, user)); 268 269 // Ask apps to allocate some cached data 270 final long targetA = doAllocateProvider(PKG_A, 0.5, 1262304000); 271 final long targetB = doAllocateProvider(PKG_B, 2.0, 1420070400); 272 final long totalAllocated = targetA + targetB; 273 274 // Apps using up some cache space shouldn't change how much we can 275 // allocate, or how much we think is free; but it should decrease real 276 // disk space. 277 if (stats.isQuotaSupported(filesUuid)) { 278 assertMostlyEquals(beforeAllocatable, 279 sm.getAllocatableBytes(filesUuid), 10 * MB_IN_BYTES); 280 assertMostlyEquals(beforeFree, 281 stats.getFreeBytes(filesUuid), 10 * MB_IN_BYTES); 282 } else { 283 assertMostlyEquals(beforeAllocatable - totalAllocated, 284 sm.getAllocatableBytes(filesUuid), 10 * MB_IN_BYTES); 285 assertMostlyEquals(beforeFree - totalAllocated, 286 stats.getFreeBytes(filesUuid), 10 * MB_IN_BYTES); 287 } 288 assertMostlyEquals(beforeRaw - totalAllocated, 289 filesDir.getUsableSpace(), 10 * MB_IN_BYTES); 290 291 assertMostlyEquals(targetA, getCacheBytes(PKG_A, user)); 292 assertMostlyEquals(targetB, getCacheBytes(PKG_B, user)); 293 294 // Allocate some space for ourselves, which should trim away at 295 // over-quota app first, even though its files are newer. 296 final long clear1 = filesDir.getUsableSpace() + (targetB / 2); 297 if (stats.isQuotaSupported(filesUuid)) { 298 sm.allocateBytes(filesUuid, clear1); 299 } else { 300 UiDevice.getInstance(getInstrumentation()) 301 .executeShellCommand("pm trim-caches " + clear1 + " " + pmUuid); 302 } 303 304 assertMostlyEquals(targetA, getCacheBytes(PKG_A, user)); 305 assertMostlyEquals(targetB / 2, getCacheBytes(PKG_B, user), 2 * MB_IN_BYTES); 306 307 // Allocate some more space for ourselves, which should now start 308 // trimming away at older app. Since we pivot between the two apps once 309 // they're tied for cache ratios, we expect to clear about half of the 310 // remaining space from each of them. 311 final long clear2 = filesDir.getUsableSpace() + (targetB / 2); 312 if (stats.isQuotaSupported(filesUuid)) { 313 sm.allocateBytes(filesUuid, clear2); 314 } else { 315 UiDevice.getInstance(getInstrumentation()) 316 .executeShellCommand("pm trim-caches " + clear2 + " " + pmUuid); 317 } 318 319 assertMostlyEquals(targetA / 2, getCacheBytes(PKG_A, user), 2 * MB_IN_BYTES); 320 assertMostlyEquals(targetA / 2, getCacheBytes(PKG_B, user), 2 * MB_IN_BYTES); 321 } 322 testCacheBehavior()323 public void testCacheBehavior() throws Exception { 324 final Context context = getContext(); 325 final StorageManager sm = context.getSystemService(StorageManager.class); 326 final StorageStatsManager stats = context.getSystemService(StorageStatsManager.class); 327 328 final UUID filesUuid = sm.getUuidForPath(context.getFilesDir()); 329 final String pmUuid = filesUuid.equals(StorageManager.UUID_DEFAULT) ? "internal" 330 : filesUuid.toString(); 331 332 final File normal = new File(context.getCacheDir(), "normal"); 333 final File group = new File(context.getCacheDir(), "group"); 334 final File tomb = new File(context.getCacheDir(), "tomb"); 335 336 final long size = 2 * MB_IN_BYTES; 337 338 final long normalTime = 1262304000; 339 final long groupTime = 1262303000; 340 final long tombTime = 1262302000; 341 342 normal.mkdir(); 343 group.mkdir(); 344 tomb.mkdir(); 345 346 sm.setCacheBehaviorGroup(group, true); 347 sm.setCacheBehaviorTombstone(tomb, true); 348 349 final File a = useFallocate(makeUniqueFile(normal), size, normalTime); 350 final File b = useFallocate(makeUniqueFile(normal), size, normalTime); 351 final File c = useFallocate(makeUniqueFile(normal), size, normalTime); 352 353 final File d = useFallocate(makeUniqueFile(group), size, groupTime); 354 final File e = useFallocate(makeUniqueFile(group), size, groupTime); 355 final File f = useFallocate(makeUniqueFile(group), size, groupTime); 356 357 final File g = useFallocate(makeUniqueFile(tomb), size, tombTime); 358 final File h = useFallocate(makeUniqueFile(tomb), size, tombTime); 359 final File i = useFallocate(makeUniqueFile(tomb), size, tombTime); 360 361 normal.setLastModified(normalTime); 362 group.setLastModified(groupTime); 363 tomb.setLastModified(tombTime); 364 365 final long clear1 = group.getUsableSpace() + (8 * MB_IN_BYTES); 366 if (stats.isQuotaSupported(filesUuid)) { 367 sm.allocateBytes(filesUuid, clear1); 368 } else { 369 UiDevice.getInstance(getInstrumentation()) 370 .executeShellCommand("pm trim-caches " + clear1 + " " + pmUuid); 371 } 372 373 assertTrue(a.exists()); 374 assertTrue(b.exists()); 375 assertTrue(c.exists()); 376 assertFalse(group.exists()); 377 assertFalse(d.exists()); 378 assertFalse(e.exists()); 379 assertFalse(f.exists()); 380 assertTrue(g.exists()); assertEquals(0, g.length()); 381 assertTrue(h.exists()); assertEquals(0, h.length()); 382 assertTrue(i.exists()); assertEquals(0, i.length()); 383 } 384 getCacheBytes(String pkg, UserHandle user)385 private long getCacheBytes(String pkg, UserHandle user) throws Exception { 386 return getContext().getSystemService(StorageStatsManager.class) 387 .queryStatsForPackage(UUID_DEFAULT, pkg, user).getCacheBytes(); 388 } 389 doAllocateReceiver(String pkg, double fraction, long time)390 private long doAllocateReceiver(String pkg, double fraction, long time) throws Exception { 391 final CountDownLatch latch = new CountDownLatch(1); 392 final Intent intent = new Intent(); 393 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 394 intent.setComponent(new ComponentName(pkg, UtilsReceiver.class.getName())); 395 intent.putExtra(UtilsReceiver.EXTRA_FRACTION, fraction); 396 intent.putExtra(UtilsReceiver.EXTRA_TIME, time); 397 final MutableLong bytes = new MutableLong(0); 398 getInstrumentation().getTargetContext().sendOrderedBroadcast(intent, null, 399 new BroadcastReceiver() { 400 @Override 401 public void onReceive(Context context, Intent intent) { 402 bytes.value = getResultExtras(false).getLong(UtilsReceiver.EXTRA_BYTES); 403 latch.countDown(); 404 } 405 }, null, Activity.RESULT_CANCELED, null, null); 406 latch.await(30, TimeUnit.SECONDS); 407 return bytes.value; 408 } 409 doAllocateProvider(String pkg, double fraction, long time)410 private long doAllocateProvider(String pkg, double fraction, long time) throws Exception { 411 final Bundle args = new Bundle(); 412 args.putDouble(UtilsReceiver.EXTRA_FRACTION, fraction); 413 args.putLong(UtilsReceiver.EXTRA_TIME, time); 414 415 try (final ContentProviderClient client = getContext().getContentResolver() 416 .acquireContentProviderClient(pkg)) { 417 final Bundle res = client.call(pkg, pkg, args); 418 return res.getLong(UtilsReceiver.EXTRA_BYTES); 419 } 420 } 421 } 422