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.server.usage; 18 19 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 20 21 import static com.android.internal.util.ArrayUtils.defeatNullable; 22 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; 23 import static com.android.server.usage.StorageStatsManagerLocal.StorageStatsAugmenter; 24 25 import android.Manifest; 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.annotation.UserIdInt; 29 import android.app.AppOpsManager; 30 import android.app.usage.ExternalStorageStats; 31 import android.app.usage.IStorageStatsManager; 32 import android.app.usage.StorageStats; 33 import android.app.usage.UsageStatsManagerInternal; 34 import android.content.BroadcastReceiver; 35 import android.content.ContentResolver; 36 import android.content.Context; 37 import android.content.Intent; 38 import android.content.IntentFilter; 39 import android.content.pm.ApplicationInfo; 40 import android.content.pm.PackageManager; 41 import android.content.pm.PackageManager.NameNotFoundException; 42 import android.content.pm.PackageStats; 43 import android.content.pm.ParceledListSlice; 44 import android.content.pm.UserInfo; 45 import android.net.Uri; 46 import android.os.Binder; 47 import android.os.Build; 48 import android.os.Environment; 49 import android.os.FileUtils; 50 import android.os.Handler; 51 import android.os.Looper; 52 import android.os.Message; 53 import android.os.ParcelableException; 54 import android.os.StatFs; 55 import android.os.SystemProperties; 56 import android.os.Trace; 57 import android.os.UserHandle; 58 import android.os.UserManager; 59 import android.os.storage.CrateInfo; 60 import android.os.storage.CrateMetadata; 61 import android.os.storage.StorageEventListener; 62 import android.os.storage.StorageManager; 63 import android.os.storage.VolumeInfo; 64 import android.provider.DeviceConfig; 65 import android.provider.Settings; 66 import android.text.TextUtils; 67 import android.text.format.DateUtils; 68 import android.util.ArrayMap; 69 import android.util.DataUnit; 70 import android.util.Pair; 71 import android.util.Slog; 72 import android.util.SparseLongArray; 73 74 import com.android.internal.annotations.GuardedBy; 75 import com.android.internal.annotations.VisibleForTesting; 76 import com.android.internal.util.ArrayUtils; 77 import com.android.internal.util.Preconditions; 78 import com.android.server.IoThread; 79 import com.android.server.LocalManagerRegistry; 80 import com.android.server.LocalServices; 81 import com.android.server.SystemService; 82 import com.android.server.pm.Installer; 83 import com.android.server.pm.Installer.InstallerException; 84 import com.android.server.storage.CacheQuotaStrategy; 85 86 import java.io.File; 87 import java.io.FileNotFoundException; 88 import java.io.IOException; 89 import java.util.ArrayList; 90 import java.util.Collections; 91 import java.util.List; 92 import java.util.concurrent.CopyOnWriteArrayList; 93 import java.util.function.Consumer; 94 95 public class StorageStatsService extends IStorageStatsManager.Stub { 96 private static final String TAG = "StorageStatsService"; 97 98 private static final String PROP_STORAGE_CRATES = "fw.storage_crates"; 99 private static final String PROP_DISABLE_QUOTA = "fw.disable_quota"; 100 private static final String PROP_VERIFY_STORAGE = "fw.verify_storage"; 101 102 private static final long DELAY_CHECK_STORAGE_DELTA = 30 * DateUtils.SECOND_IN_MILLIS; 103 private static final long DELAY_RECALCULATE_QUOTAS = 10 * DateUtils.HOUR_IN_MILLIS; 104 private static final long DEFAULT_QUOTA = DataUnit.MEBIBYTES.toBytes(64); 105 106 public static class Lifecycle extends SystemService { 107 private StorageStatsService mService; 108 Lifecycle(Context context)109 public Lifecycle(Context context) { 110 super(context); 111 } 112 113 @Override onStart()114 public void onStart() { 115 mService = new StorageStatsService(getContext()); 116 publishBinderService(Context.STORAGE_STATS_SERVICE, mService); 117 } 118 } 119 120 private final Context mContext; 121 private final AppOpsManager mAppOps; 122 private final UserManager mUser; 123 private final PackageManager mPackage; 124 private final StorageManager mStorage; 125 private final ArrayMap<String, SparseLongArray> mCacheQuotas; 126 127 private final Installer mInstaller; 128 private final H mHandler; 129 130 private final CopyOnWriteArrayList<Pair<String, StorageStatsAugmenter>> 131 mStorageStatsAugmenters = new CopyOnWriteArrayList<>(); 132 133 @GuardedBy("mLock") 134 private int 135 mStorageThresholdPercentHigh = StorageManager.DEFAULT_STORAGE_THRESHOLD_PERCENT_HIGH; 136 137 private final Object mLock = new Object(); 138 StorageStatsService(Context context)139 public StorageStatsService(Context context) { 140 mContext = Preconditions.checkNotNull(context); 141 mAppOps = Preconditions.checkNotNull(context.getSystemService(AppOpsManager.class)); 142 mUser = Preconditions.checkNotNull(context.getSystemService(UserManager.class)); 143 mPackage = Preconditions.checkNotNull(context.getPackageManager()); 144 mStorage = Preconditions.checkNotNull(context.getSystemService(StorageManager.class)); 145 mCacheQuotas = new ArrayMap<>(); 146 147 mInstaller = new Installer(context); 148 mInstaller.onStart(); 149 invalidateMounts(); 150 151 mHandler = new H(IoThread.get().getLooper()); 152 mHandler.sendEmptyMessage(H.MSG_LOAD_CACHED_QUOTAS_FROM_FILE); 153 154 mStorage.registerListener(new StorageEventListener() { 155 @Override 156 public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { 157 switch (vol.type) { 158 case VolumeInfo.TYPE_PUBLIC: 159 case VolumeInfo.TYPE_PRIVATE: 160 case VolumeInfo.TYPE_EMULATED: 161 if (newState == VolumeInfo.STATE_MOUNTED) { 162 invalidateMounts(); 163 } 164 } 165 } 166 }); 167 168 LocalManagerRegistry.addManager(StorageStatsManagerLocal.class, new LocalService()); 169 170 IntentFilter prFilter = new IntentFilter(); 171 prFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 172 prFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED); 173 prFilter.addDataScheme("package"); 174 mContext.registerReceiver(new BroadcastReceiver() { 175 @Override public void onReceive(Context context, Intent intent) { 176 String action = intent.getAction(); 177 if (Intent.ACTION_PACKAGE_REMOVED.equals(action) 178 || Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) { 179 mHandler.removeMessages(H.MSG_PACKAGE_REMOVED); 180 mHandler.sendEmptyMessage(H.MSG_PACKAGE_REMOVED); 181 } 182 } 183 }, prFilter); 184 185 updateConfig(); 186 DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, 187 mContext.getMainExecutor(), properties -> updateConfig()); 188 } 189 updateConfig()190 private void updateConfig() { 191 synchronized (mLock) { 192 mStorageThresholdPercentHigh = DeviceConfig.getInt( 193 DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, 194 StorageManager.STORAGE_THRESHOLD_PERCENT_HIGH_KEY, 195 StorageManager.DEFAULT_STORAGE_THRESHOLD_PERCENT_HIGH); 196 } 197 } 198 invalidateMounts()199 private void invalidateMounts() { 200 try { 201 mInstaller.invalidateMounts(); 202 } catch (InstallerException e) { 203 Slog.wtf(TAG, "Failed to invalidate mounts", e); 204 } 205 } 206 enforceStatsPermission(int callingUid, String callingPackage)207 private void enforceStatsPermission(int callingUid, String callingPackage) { 208 final String errMsg = checkStatsPermission(callingUid, callingPackage, true); 209 if (errMsg != null) { 210 throw new SecurityException(errMsg); 211 } 212 } 213 checkStatsPermission(int callingUid, String callingPackage, boolean noteOp)214 private String checkStatsPermission(int callingUid, String callingPackage, boolean noteOp) { 215 final int mode; 216 if (noteOp) { 217 mode = mAppOps.noteOp(AppOpsManager.OP_GET_USAGE_STATS, callingUid, callingPackage); 218 } else { 219 mode = mAppOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS, callingUid, callingPackage); 220 } 221 switch (mode) { 222 case AppOpsManager.MODE_ALLOWED: 223 return null; 224 case AppOpsManager.MODE_DEFAULT: 225 if (mContext.checkCallingOrSelfPermission( 226 Manifest.permission.PACKAGE_USAGE_STATS) == PERMISSION_GRANTED) { 227 return null; 228 } else { 229 return "Caller does not have " + Manifest.permission.PACKAGE_USAGE_STATS 230 + "; callingPackage=" + callingPackage + ", callingUid=" + callingUid; 231 } 232 default: 233 return "Package " + callingPackage + " from UID " + callingUid 234 + " blocked by mode " + mode; 235 } 236 } 237 238 @Override isQuotaSupported(String volumeUuid, String callingPackage)239 public boolean isQuotaSupported(String volumeUuid, String callingPackage) { 240 try { 241 return mInstaller.isQuotaSupported(volumeUuid); 242 } catch (InstallerException e) { 243 throw new ParcelableException(new IOException(e.getMessage())); 244 } 245 } 246 247 @Override isReservedSupported(String volumeUuid, String callingPackage)248 public boolean isReservedSupported(String volumeUuid, String callingPackage) { 249 if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) { 250 return SystemProperties.getBoolean(StorageManager.PROP_HAS_RESERVED, false) 251 || Build.IS_ARC; 252 } else { 253 return false; 254 } 255 } 256 257 @Override getTotalBytes(String volumeUuid, String callingPackage)258 public long getTotalBytes(String volumeUuid, String callingPackage) { 259 // NOTE: No permissions required 260 261 if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) { 262 // As a safety measure, use the original implementation for the devices 263 // with storage size <= 512GB to prevent any potential regressions 264 final long roundedUserspaceBytes = mStorage.getPrimaryStorageSize(); 265 if (roundedUserspaceBytes <= DataUnit.GIGABYTES.toBytes(512)) { 266 return roundedUserspaceBytes; 267 } 268 269 // Since 1TB devices can actually have either 1000GB or 1024GB, 270 // get the block device size and do just a small rounding if any at all 271 final long totalBytes = mStorage.getInternalStorageBlockDeviceSize(); 272 final long totalBytesRounded = FileUtils.roundStorageSize(totalBytes); 273 // If the storage size is 997GB-999GB, round it to a 1000GB to show 274 // 1TB in UI instead of 0.99TB. Same for 2TB, 4TB, 8TB etc. 275 if (totalBytesRounded - totalBytes <= DataUnit.GIGABYTES.toBytes(3)) { 276 return totalBytesRounded; 277 } else { 278 return totalBytes; 279 } 280 } else { 281 final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid); 282 if (vol == null) { 283 throw new ParcelableException( 284 new IOException("Failed to find storage device for UUID " + volumeUuid)); 285 } 286 return FileUtils.roundStorageSize(vol.disk.size); 287 } 288 } 289 290 @Override getFreeBytes(String volumeUuid, String callingPackage)291 public long getFreeBytes(String volumeUuid, String callingPackage) { 292 // NOTE: No permissions required 293 294 final long token = Binder.clearCallingIdentity(); 295 try { 296 final File path; 297 try { 298 path = mStorage.findPathForUuid(volumeUuid); 299 } catch (FileNotFoundException e) { 300 throw new ParcelableException(e); 301 } 302 303 // Free space is usable bytes plus any cached data that we're 304 // willing to automatically clear. To avoid user confusion, this 305 // logic should be kept in sync with getAllocatableBytes(). 306 long freeBytes; 307 if (isQuotaSupported(volumeUuid, PLATFORM_PACKAGE_NAME)) { 308 final long cacheTotal = getCacheBytes(volumeUuid, PLATFORM_PACKAGE_NAME); 309 final long cacheReserved = mStorage.getStorageCacheBytes(path, 0); 310 final long cacheClearable = Math.max(0, cacheTotal - cacheReserved); 311 312 freeBytes = path.getUsableSpace() + cacheClearable; 313 } else { 314 freeBytes = path.getUsableSpace(); 315 } 316 317 Slog.d(TAG, "getFreeBytes: " + freeBytes); 318 return freeBytes; 319 } finally { 320 Binder.restoreCallingIdentity(token); 321 } 322 } 323 324 @Override getCacheBytes(String volumeUuid, String callingPackage)325 public long getCacheBytes(String volumeUuid, String callingPackage) { 326 enforceStatsPermission(Binder.getCallingUid(), callingPackage); 327 328 long cacheBytes = 0; 329 for (UserInfo user : mUser.getUsers()) { 330 final StorageStats stats = queryStatsForUser(volumeUuid, user.id, null); 331 cacheBytes += stats.cacheBytes; 332 } 333 return cacheBytes; 334 } 335 336 @Override getCacheQuotaBytes(String volumeUuid, int uid, String callingPackage)337 public long getCacheQuotaBytes(String volumeUuid, int uid, String callingPackage) { 338 enforceStatsPermission(Binder.getCallingUid(), callingPackage); 339 340 if (mCacheQuotas.containsKey(volumeUuid)) { 341 final SparseLongArray uidMap = mCacheQuotas.get(volumeUuid); 342 return uidMap.get(uid, DEFAULT_QUOTA); 343 } 344 345 return DEFAULT_QUOTA; 346 } 347 348 @Override queryStatsForPackage(String volumeUuid, String packageName, int userId, String callingPackage)349 public StorageStats queryStatsForPackage(String volumeUuid, String packageName, int userId, 350 String callingPackage) { 351 if (userId != UserHandle.getCallingUserId()) { 352 mContext.enforceCallingOrSelfPermission( 353 android.Manifest.permission.INTERACT_ACROSS_USERS, TAG); 354 } 355 356 final ApplicationInfo appInfo; 357 try { 358 appInfo = mPackage.getApplicationInfoAsUser(packageName, 359 PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); 360 } catch (NameNotFoundException e) { 361 throw new ParcelableException(e); 362 } 363 364 final boolean callerHasStatsPermission; 365 if (Binder.getCallingUid() == appInfo.uid) { 366 // No permissions required when asking about themselves. We still check since it is 367 // needed later on but don't throw if caller doesn't have the permission. 368 callerHasStatsPermission = checkStatsPermission( 369 Binder.getCallingUid(), callingPackage, false) == null; 370 } else { 371 enforceStatsPermission(Binder.getCallingUid(), callingPackage); 372 callerHasStatsPermission = true; 373 } 374 375 if (defeatNullable(mPackage.getPackagesForUid(appInfo.uid)).length == 1) { 376 // Only one package inside UID means we can fast-path 377 return queryStatsForUid(volumeUuid, appInfo.uid, callingPackage); 378 } else { 379 // Multiple packages means we need to go manual 380 final int appId = UserHandle.getUserId(appInfo.uid); 381 final String[] packageNames = new String[] { packageName }; 382 final long[] ceDataInodes = new long[1]; 383 String[] codePaths = new String[0]; 384 385 if (appInfo.isSystemApp() && !appInfo.isUpdatedSystemApp()) { 386 // We don't count code baked into system image 387 } else { 388 codePaths = ArrayUtils.appendElement(String.class, codePaths, 389 appInfo.getCodePath()); 390 } 391 392 final PackageStats stats = new PackageStats(TAG); 393 try { 394 mInstaller.getAppSize(volumeUuid, packageNames, userId, 0, 395 appId, ceDataInodes, codePaths, stats); 396 } catch (InstallerException e) { 397 throw new ParcelableException(new IOException(e.getMessage())); 398 } 399 if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) { 400 UserHandle userHandle = UserHandle.of(userId); 401 forEachStorageStatsAugmenter((storageStatsAugmenter) -> { 402 storageStatsAugmenter.augmentStatsForPackageForUser(stats, 403 packageName, userHandle, callerHasStatsPermission); 404 }, "queryStatsForPackage"); 405 } 406 return translate(stats); 407 } 408 } 409 410 @Override queryStatsForUid(String volumeUuid, int uid, String callingPackage)411 public StorageStats queryStatsForUid(String volumeUuid, int uid, String callingPackage) { 412 final int userId = UserHandle.getUserId(uid); 413 final int appId = UserHandle.getAppId(uid); 414 415 if (userId != UserHandle.getCallingUserId()) { 416 mContext.enforceCallingOrSelfPermission( 417 android.Manifest.permission.INTERACT_ACROSS_USERS, TAG); 418 } 419 420 final boolean callerHasStatsPermission; 421 if (Binder.getCallingUid() == uid) { 422 // No permissions required when asking about themselves. We still check since it is 423 // needed later on but don't throw if caller doesn't have the permission. 424 callerHasStatsPermission = checkStatsPermission( 425 Binder.getCallingUid(), callingPackage, false) == null; 426 } else { 427 enforceStatsPermission(Binder.getCallingUid(), callingPackage); 428 callerHasStatsPermission = true; 429 } 430 431 final String[] packageNames = defeatNullable(mPackage.getPackagesForUid(uid)); 432 final long[] ceDataInodes = new long[packageNames.length]; 433 String[] codePaths = new String[0]; 434 435 for (int i = 0; i < packageNames.length; i++) { 436 try { 437 final ApplicationInfo appInfo = mPackage.getApplicationInfoAsUser(packageNames[i], 438 PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); 439 if (appInfo.isSystemApp() && !appInfo.isUpdatedSystemApp()) { 440 // We don't count code baked into system image 441 } else { 442 codePaths = ArrayUtils.appendElement(String.class, codePaths, 443 appInfo.getCodePath()); 444 } 445 } catch (NameNotFoundException e) { 446 throw new ParcelableException(e); 447 } 448 } 449 450 final PackageStats stats = new PackageStats(TAG); 451 try { 452 mInstaller.getAppSize(volumeUuid, packageNames, userId, getDefaultFlags(), 453 appId, ceDataInodes, codePaths, stats); 454 455 if (SystemProperties.getBoolean(PROP_VERIFY_STORAGE, false)) { 456 final PackageStats manualStats = new PackageStats(TAG); 457 mInstaller.getAppSize(volumeUuid, packageNames, userId, 0, 458 appId, ceDataInodes, codePaths, manualStats); 459 checkEquals("UID " + uid, manualStats, stats); 460 } 461 } catch (InstallerException e) { 462 throw new ParcelableException(new IOException(e.getMessage())); 463 } 464 465 if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) { 466 forEachStorageStatsAugmenter((storageStatsAugmenter) -> { 467 storageStatsAugmenter.augmentStatsForUid(stats, uid, callerHasStatsPermission); 468 }, "queryStatsForUid"); 469 } 470 return translate(stats); 471 } 472 473 @Override queryStatsForUser(String volumeUuid, int userId, String callingPackage)474 public StorageStats queryStatsForUser(String volumeUuid, int userId, String callingPackage) { 475 if (userId != UserHandle.getCallingUserId()) { 476 mContext.enforceCallingOrSelfPermission( 477 android.Manifest.permission.INTERACT_ACROSS_USERS, TAG); 478 } 479 480 // Always require permission to see user-level stats 481 enforceStatsPermission(Binder.getCallingUid(), callingPackage); 482 483 final int[] appIds = getAppIds(userId); 484 final PackageStats stats = new PackageStats(TAG); 485 try { 486 mInstaller.getUserSize(volumeUuid, userId, getDefaultFlags(), appIds, stats); 487 488 if (SystemProperties.getBoolean(PROP_VERIFY_STORAGE, false)) { 489 final PackageStats manualStats = new PackageStats(TAG); 490 mInstaller.getUserSize(volumeUuid, userId, 0, appIds, manualStats); 491 checkEquals("User " + userId, manualStats, stats); 492 } 493 } catch (InstallerException e) { 494 throw new ParcelableException(new IOException(e.getMessage())); 495 } 496 if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) { 497 UserHandle userHandle = UserHandle.of(userId); 498 forEachStorageStatsAugmenter((storageStatsAugmenter) -> { 499 storageStatsAugmenter.augmentStatsForUser(stats, userHandle); 500 }, "queryStatsForUser"); 501 } 502 return translate(stats); 503 } 504 505 @Override queryExternalStatsForUser(String volumeUuid, int userId, String callingPackage)506 public ExternalStorageStats queryExternalStatsForUser(String volumeUuid, int userId, 507 String callingPackage) { 508 if (userId != UserHandle.getCallingUserId()) { 509 mContext.enforceCallingOrSelfPermission( 510 android.Manifest.permission.INTERACT_ACROSS_USERS, TAG); 511 } 512 513 // Always require permission to see user-level stats 514 enforceStatsPermission(Binder.getCallingUid(), callingPackage); 515 516 final int[] appIds = getAppIds(userId); 517 final long[] stats; 518 try { 519 stats = mInstaller.getExternalSize(volumeUuid, userId, getDefaultFlags(), appIds); 520 521 if (SystemProperties.getBoolean(PROP_VERIFY_STORAGE, false)) { 522 final long[] manualStats = mInstaller.getExternalSize(volumeUuid, userId, 0, 523 appIds); 524 checkEquals("External " + userId, manualStats, stats); 525 } 526 } catch (InstallerException e) { 527 throw new ParcelableException(new IOException(e.getMessage())); 528 } 529 530 final ExternalStorageStats res = new ExternalStorageStats(); 531 res.totalBytes = stats[0]; 532 res.audioBytes = stats[1]; 533 res.videoBytes = stats[2]; 534 res.imageBytes = stats[3]; 535 res.appBytes = stats[4]; 536 res.obbBytes = stats[5]; 537 return res; 538 } 539 getAppIds(int userId)540 private int[] getAppIds(int userId) { 541 int[] appIds = null; 542 for (ApplicationInfo app : mPackage.getInstalledApplicationsAsUser( 543 PackageManager.MATCH_UNINSTALLED_PACKAGES, userId)) { 544 final int appId = UserHandle.getAppId(app.uid); 545 if (!ArrayUtils.contains(appIds, appId)) { 546 appIds = ArrayUtils.appendInt(appIds, appId); 547 } 548 } 549 return appIds; 550 } 551 getDefaultFlags()552 private static int getDefaultFlags() { 553 if (SystemProperties.getBoolean(PROP_DISABLE_QUOTA, false)) { 554 return 0; 555 } else { 556 return Installer.FLAG_USE_QUOTA; 557 } 558 } 559 checkEquals(String msg, long[] a, long[] b)560 private static void checkEquals(String msg, long[] a, long[] b) { 561 for (int i = 0; i < a.length; i++) { 562 checkEquals(msg + "[" + i + "]", a[i], b[i]); 563 } 564 } 565 checkEquals(String msg, PackageStats a, PackageStats b)566 private static void checkEquals(String msg, PackageStats a, PackageStats b) { 567 checkEquals(msg + " codeSize", a.codeSize, b.codeSize); 568 checkEquals(msg + " dataSize", a.dataSize, b.dataSize); 569 checkEquals(msg + " cacheSize", a.cacheSize, b.cacheSize); 570 checkEquals(msg + " externalCodeSize", a.externalCodeSize, b.externalCodeSize); 571 checkEquals(msg + " externalDataSize", a.externalDataSize, b.externalDataSize); 572 checkEquals(msg + " externalCacheSize", a.externalCacheSize, b.externalCacheSize); 573 } 574 checkEquals(String msg, long expected, long actual)575 private static void checkEquals(String msg, long expected, long actual) { 576 if (expected != actual) { 577 Slog.e(TAG, msg + " expected " + expected + " actual " + actual); 578 } 579 } 580 translate(PackageStats stats)581 private static StorageStats translate(PackageStats stats) { 582 final StorageStats res = new StorageStats(); 583 res.codeBytes = stats.codeSize + stats.externalCodeSize; 584 res.dataBytes = stats.dataSize + stats.externalDataSize; 585 res.cacheBytes = stats.cacheSize + stats.externalCacheSize; 586 res.externalCacheBytes = stats.externalCacheSize; 587 return res; 588 } 589 590 private class H extends Handler { 591 private static final int MSG_CHECK_STORAGE_DELTA = 100; 592 private static final int MSG_LOAD_CACHED_QUOTAS_FROM_FILE = 101; 593 private static final int MSG_RECALCULATE_QUOTAS = 102; 594 private static final int MSG_PACKAGE_REMOVED = 103; 595 /** 596 * By only triggering a re-calculation after the storage has changed sizes, we can avoid 597 * recalculating quotas too often. Minimum change delta high and low define the 598 * percentage of change we need to see before we recalculate quotas when the device has 599 * enough storage space (more than mStorageThresholdPercentHigh of total 600 * free) and in low storage condition respectively. 601 */ 602 private static final long MINIMUM_CHANGE_DELTA_PERCENT_HIGH = 5; 603 private static final long MINIMUM_CHANGE_DELTA_PERCENT_LOW = 2; 604 private static final int UNSET = -1; 605 private static final boolean DEBUG = false; 606 607 private final StatFs mStats; 608 private long mPreviousBytes; 609 private long mTotalBytes; 610 H(Looper looper)611 public H(Looper looper) { 612 super(looper); 613 // TODO: Handle all private volumes. 614 mStats = new StatFs(Environment.getDataDirectory().getAbsolutePath()); 615 mPreviousBytes = mStats.getAvailableBytes(); 616 mTotalBytes = mStats.getTotalBytes(); 617 } 618 handleMessage(Message msg)619 public void handleMessage(Message msg) { 620 if (DEBUG) { 621 Slog.v(TAG, ">>> handling " + msg.what); 622 } 623 624 if (!isCacheQuotaCalculationsEnabled(mContext.getContentResolver())) { 625 return; 626 } 627 628 switch (msg.what) { 629 case MSG_CHECK_STORAGE_DELTA: { 630 mStats.restat(Environment.getDataDirectory().getAbsolutePath()); 631 long bytesDelta = Math.abs(mPreviousBytes - mStats.getAvailableBytes()); 632 long bytesDeltaThreshold; 633 synchronized (mLock) { 634 if (mStats.getAvailableBytes() > mTotalBytes 635 * mStorageThresholdPercentHigh / 100) { 636 bytesDeltaThreshold = mTotalBytes 637 * MINIMUM_CHANGE_DELTA_PERCENT_HIGH / 100; 638 } else { 639 bytesDeltaThreshold = mTotalBytes 640 * MINIMUM_CHANGE_DELTA_PERCENT_LOW / 100; 641 } 642 } 643 if (bytesDelta > bytesDeltaThreshold) { 644 mPreviousBytes = mStats.getAvailableBytes(); 645 recalculateQuotas(getInitializedStrategy()); 646 notifySignificantDelta(); 647 } 648 sendEmptyMessageDelayed(MSG_CHECK_STORAGE_DELTA, DELAY_CHECK_STORAGE_DELTA); 649 break; 650 } 651 case MSG_LOAD_CACHED_QUOTAS_FROM_FILE: { 652 CacheQuotaStrategy strategy = getInitializedStrategy(); 653 mPreviousBytes = UNSET; 654 try { 655 mPreviousBytes = strategy.setupQuotasFromFile(); 656 } catch (IOException e) { 657 Slog.e(TAG, "An error occurred while reading the cache quota file.", e); 658 } catch (IllegalStateException e) { 659 Slog.e(TAG, "Cache quota XML file is malformed?", e); 660 } 661 662 // If errors occurred getting the quotas from disk, let's re-calc them. 663 if (mPreviousBytes < 0) { 664 mStats.restat(Environment.getDataDirectory().getAbsolutePath()); 665 mPreviousBytes = mStats.getAvailableBytes(); 666 recalculateQuotas(strategy); 667 } 668 sendEmptyMessageDelayed(MSG_CHECK_STORAGE_DELTA, DELAY_CHECK_STORAGE_DELTA); 669 sendEmptyMessageDelayed(MSG_RECALCULATE_QUOTAS, DELAY_RECALCULATE_QUOTAS); 670 break; 671 } 672 case MSG_RECALCULATE_QUOTAS: { 673 recalculateQuotas(getInitializedStrategy()); 674 sendEmptyMessageDelayed(MSG_RECALCULATE_QUOTAS, DELAY_RECALCULATE_QUOTAS); 675 break; 676 } 677 case MSG_PACKAGE_REMOVED: { 678 // recalculate quotas when package is removed 679 recalculateQuotas(getInitializedStrategy()); 680 break; 681 } 682 default: 683 if (DEBUG) { 684 Slog.v(TAG, ">>> default message case "); 685 } 686 return; 687 } 688 } 689 recalculateQuotas(CacheQuotaStrategy strategy)690 private void recalculateQuotas(CacheQuotaStrategy strategy) { 691 if (DEBUG) { 692 Slog.v(TAG, ">>> recalculating quotas "); 693 } 694 695 strategy.recalculateQuotas(); 696 } 697 getInitializedStrategy()698 private CacheQuotaStrategy getInitializedStrategy() { 699 UsageStatsManagerInternal usageStatsManager = 700 LocalServices.getService(UsageStatsManagerInternal.class); 701 return new CacheQuotaStrategy(mContext, usageStatsManager, mInstaller, mCacheQuotas); 702 } 703 } 704 705 @VisibleForTesting isCacheQuotaCalculationsEnabled(ContentResolver resolver)706 static boolean isCacheQuotaCalculationsEnabled(ContentResolver resolver) { 707 return Settings.Global.getInt( 708 resolver, Settings.Global.ENABLE_CACHE_QUOTA_CALCULATION, 1) != 0; 709 } 710 711 /** 712 * Hacky way of notifying that disk space has changed significantly; we do 713 * this to cause "available space" values to be requeried. 714 */ notifySignificantDelta()715 void notifySignificantDelta() { 716 mContext.getContentResolver().notifyChange( 717 Uri.parse("content://com.android.externalstorage.documents/"), null, false); 718 } 719 checkCratesEnable()720 private static void checkCratesEnable() { 721 final boolean enable = SystemProperties.getBoolean(PROP_STORAGE_CRATES, false); 722 if (!enable) { 723 throw new IllegalStateException("Storage Crate feature is disabled."); 724 } 725 } 726 727 /** 728 * To enforce the calling or self to have the {@link android.Manifest.permission#MANAGE_CRATES} 729 * permission. 730 * @param callingUid the calling uid 731 * @param callingPackage the calling package name 732 */ enforceCratesPermission(int callingUid, String callingPackage)733 private void enforceCratesPermission(int callingUid, String callingPackage) { 734 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_CRATES, 735 callingPackage); 736 } 737 738 /** 739 * To copy from CrateMetadata instances into CrateInfo instances. 740 */ 741 @NonNull convertCrateInfoFrom(@ullable CrateMetadata[] crateMetadatas)742 private static List<CrateInfo> convertCrateInfoFrom(@Nullable CrateMetadata[] crateMetadatas) { 743 if (ArrayUtils.isEmpty(crateMetadatas)) { 744 return Collections.EMPTY_LIST; 745 } 746 747 ArrayList<CrateInfo> crateInfos = new ArrayList<>(); 748 for (CrateMetadata crateMetadata : crateMetadatas) { 749 if (crateMetadata == null || TextUtils.isEmpty(crateMetadata.id) 750 || TextUtils.isEmpty(crateMetadata.packageName)) { 751 continue; 752 } 753 754 CrateInfo crateInfo = CrateInfo.copyFrom(crateMetadata.uid, 755 crateMetadata.packageName, crateMetadata.id); 756 if (crateInfo == null) { 757 continue; 758 } 759 760 crateInfos.add(crateInfo); 761 } 762 763 return crateInfos; 764 } 765 766 @NonNull getAppCrates(String volumeUuid, String[] packageNames, @UserIdInt int userId)767 private ParceledListSlice<CrateInfo> getAppCrates(String volumeUuid, String[] packageNames, 768 @UserIdInt int userId) { 769 try { 770 CrateMetadata[] crateMetadatas = mInstaller.getAppCrates(volumeUuid, 771 packageNames, userId); 772 return new ParceledListSlice<>(convertCrateInfoFrom(crateMetadatas)); 773 } catch (InstallerException e) { 774 throw new ParcelableException(new IOException(e.getMessage())); 775 } 776 } 777 778 @NonNull 779 @Override queryCratesForPackage(String volumeUuid, @NonNull String packageName, @UserIdInt int userId, @NonNull String callingPackage)780 public ParceledListSlice<CrateInfo> queryCratesForPackage(String volumeUuid, 781 @NonNull String packageName, @UserIdInt int userId, @NonNull String callingPackage) { 782 checkCratesEnable(); 783 if (userId != UserHandle.getCallingUserId()) { 784 mContext.enforceCallingOrSelfPermission( 785 android.Manifest.permission.INTERACT_ACROSS_USERS, TAG); 786 } 787 788 final ApplicationInfo appInfo; 789 try { 790 appInfo = mPackage.getApplicationInfoAsUser(packageName, 791 PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); 792 } catch (NameNotFoundException e) { 793 throw new ParcelableException(e); 794 } 795 796 if (Binder.getCallingUid() == appInfo.uid) { 797 // No permissions required when asking about themselves 798 } else { 799 enforceCratesPermission(Binder.getCallingUid(), callingPackage); 800 } 801 802 final String[] packageNames = new String[] { packageName }; 803 return getAppCrates(volumeUuid, packageNames, userId); 804 } 805 806 @NonNull 807 @Override queryCratesForUid(String volumeUuid, int uid, @NonNull String callingPackage)808 public ParceledListSlice<CrateInfo> queryCratesForUid(String volumeUuid, int uid, 809 @NonNull String callingPackage) { 810 checkCratesEnable(); 811 final int userId = UserHandle.getUserId(uid); 812 if (userId != UserHandle.getCallingUserId()) { 813 mContext.enforceCallingOrSelfPermission( 814 android.Manifest.permission.INTERACT_ACROSS_USERS, TAG); 815 } 816 817 if (Binder.getCallingUid() == uid) { 818 // No permissions required when asking about themselves 819 } else { 820 enforceCratesPermission(Binder.getCallingUid(), callingPackage); 821 } 822 823 final String[] packageNames = defeatNullable(mPackage.getPackagesForUid(uid)); 824 String[] validatedPackageNames = new String[0]; 825 826 for (String packageName : packageNames) { 827 if (TextUtils.isEmpty(packageName)) { 828 continue; 829 } 830 831 try { 832 final ApplicationInfo appInfo = mPackage.getApplicationInfoAsUser(packageName, 833 PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); 834 if (appInfo == null) { 835 continue; 836 } 837 838 validatedPackageNames = ArrayUtils.appendElement(String.class, 839 validatedPackageNames, packageName); 840 } catch (NameNotFoundException e) { 841 throw new ParcelableException(e); 842 } 843 } 844 845 return getAppCrates(volumeUuid, validatedPackageNames, userId); 846 } 847 848 @NonNull 849 @Override queryCratesForUser(String volumeUuid, int userId, @NonNull String callingPackage)850 public ParceledListSlice<CrateInfo> queryCratesForUser(String volumeUuid, int userId, 851 @NonNull String callingPackage) { 852 checkCratesEnable(); 853 if (userId != UserHandle.getCallingUserId()) { 854 mContext.enforceCallingOrSelfPermission( 855 android.Manifest.permission.INTERACT_ACROSS_USERS, TAG); 856 } 857 858 // Always require permission to see user-level stats 859 enforceCratesPermission(Binder.getCallingUid(), callingPackage); 860 861 try { 862 CrateMetadata[] crateMetadatas = mInstaller.getUserCrates(volumeUuid, userId); 863 return new ParceledListSlice<>(convertCrateInfoFrom(crateMetadatas)); 864 } catch (InstallerException e) { 865 throw new ParcelableException(new IOException(e.getMessage())); 866 } 867 } 868 forEachStorageStatsAugmenter(@onNull Consumer<StorageStatsAugmenter> consumer, @NonNull String queryTag)869 void forEachStorageStatsAugmenter(@NonNull Consumer<StorageStatsAugmenter> consumer, 870 @NonNull String queryTag) { 871 for (int i = 0, count = mStorageStatsAugmenters.size(); i < count; ++i) { 872 final Pair<String, StorageStatsAugmenter> pair = mStorageStatsAugmenters.get(i); 873 final String augmenterTag = pair.first; 874 final StorageStatsAugmenter storageStatsAugmenter = pair.second; 875 876 Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, queryTag + ":" + augmenterTag); 877 try { 878 consumer.accept(storageStatsAugmenter); 879 } finally { 880 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); 881 } 882 } 883 } 884 885 private class LocalService implements StorageStatsManagerLocal { 886 @Override registerStorageStatsAugmenter( @onNull StorageStatsAugmenter storageStatsAugmenter, @NonNull String tag)887 public void registerStorageStatsAugmenter( 888 @NonNull StorageStatsAugmenter storageStatsAugmenter, 889 @NonNull String tag) { 890 mStorageStatsAugmenters.add(Pair.create(tag, storageStatsAugmenter)); 891 } 892 } 893 } 894