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