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