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 return FileUtils.roundStorageSize(mStorage.getPrimaryStorageSize()); 263 } else { 264 final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid); 265 if (vol == null) { 266 throw new ParcelableException( 267 new IOException("Failed to find storage device for UUID " + volumeUuid)); 268 } 269 return FileUtils.roundStorageSize(vol.disk.size); 270 } 271 } 272 273 @Override getFreeBytes(String volumeUuid, String callingPackage)274 public long getFreeBytes(String volumeUuid, String callingPackage) { 275 // NOTE: No permissions required 276 277 final long token = Binder.clearCallingIdentity(); 278 try { 279 final File path; 280 try { 281 path = mStorage.findPathForUuid(volumeUuid); 282 } catch (FileNotFoundException e) { 283 throw new ParcelableException(e); 284 } 285 286 // Free space is usable bytes plus any cached data that we're 287 // willing to automatically clear. To avoid user confusion, this 288 // logic should be kept in sync with getAllocatableBytes(). 289 if (isQuotaSupported(volumeUuid, PLATFORM_PACKAGE_NAME)) { 290 final long cacheTotal = getCacheBytes(volumeUuid, PLATFORM_PACKAGE_NAME); 291 final long cacheReserved = mStorage.getStorageCacheBytes(path, 0); 292 final long cacheClearable = Math.max(0, cacheTotal - cacheReserved); 293 294 return path.getUsableSpace() + cacheClearable; 295 } else { 296 return path.getUsableSpace(); 297 } 298 } finally { 299 Binder.restoreCallingIdentity(token); 300 } 301 } 302 303 @Override getCacheBytes(String volumeUuid, String callingPackage)304 public long getCacheBytes(String volumeUuid, String callingPackage) { 305 enforceStatsPermission(Binder.getCallingUid(), callingPackage); 306 307 long cacheBytes = 0; 308 for (UserInfo user : mUser.getUsers()) { 309 final StorageStats stats = queryStatsForUser(volumeUuid, user.id, null); 310 cacheBytes += stats.cacheBytes; 311 } 312 return cacheBytes; 313 } 314 315 @Override getCacheQuotaBytes(String volumeUuid, int uid, String callingPackage)316 public long getCacheQuotaBytes(String volumeUuid, int uid, String callingPackage) { 317 enforceStatsPermission(Binder.getCallingUid(), callingPackage); 318 319 if (mCacheQuotas.containsKey(volumeUuid)) { 320 final SparseLongArray uidMap = mCacheQuotas.get(volumeUuid); 321 return uidMap.get(uid, DEFAULT_QUOTA); 322 } 323 324 return DEFAULT_QUOTA; 325 } 326 327 @Override queryStatsForPackage(String volumeUuid, String packageName, int userId, String callingPackage)328 public StorageStats queryStatsForPackage(String volumeUuid, String packageName, int userId, 329 String callingPackage) { 330 if (userId != UserHandle.getCallingUserId()) { 331 mContext.enforceCallingOrSelfPermission( 332 android.Manifest.permission.INTERACT_ACROSS_USERS, TAG); 333 } 334 335 final ApplicationInfo appInfo; 336 try { 337 appInfo = mPackage.getApplicationInfoAsUser(packageName, 338 PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); 339 } catch (NameNotFoundException e) { 340 throw new ParcelableException(e); 341 } 342 343 final boolean callerHasStatsPermission; 344 if (Binder.getCallingUid() == appInfo.uid) { 345 // No permissions required when asking about themselves. We still check since it is 346 // needed later on but don't throw if caller doesn't have the permission. 347 callerHasStatsPermission = checkStatsPermission( 348 Binder.getCallingUid(), callingPackage, false) == null; 349 } else { 350 enforceStatsPermission(Binder.getCallingUid(), callingPackage); 351 callerHasStatsPermission = true; 352 } 353 354 if (defeatNullable(mPackage.getPackagesForUid(appInfo.uid)).length == 1) { 355 // Only one package inside UID means we can fast-path 356 return queryStatsForUid(volumeUuid, appInfo.uid, callingPackage); 357 } else { 358 // Multiple packages means we need to go manual 359 final int appId = UserHandle.getUserId(appInfo.uid); 360 final String[] packageNames = new String[] { packageName }; 361 final long[] ceDataInodes = new long[1]; 362 String[] codePaths = new String[0]; 363 364 if (appInfo.isSystemApp() && !appInfo.isUpdatedSystemApp()) { 365 // We don't count code baked into system image 366 } else { 367 codePaths = ArrayUtils.appendElement(String.class, codePaths, 368 appInfo.getCodePath()); 369 } 370 371 final PackageStats stats = new PackageStats(TAG); 372 try { 373 mInstaller.getAppSize(volumeUuid, packageNames, userId, 0, 374 appId, ceDataInodes, codePaths, stats); 375 } catch (InstallerException e) { 376 throw new ParcelableException(new IOException(e.getMessage())); 377 } 378 if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) { 379 UserHandle userHandle = UserHandle.of(userId); 380 forEachStorageStatsAugmenter((storageStatsAugmenter) -> { 381 storageStatsAugmenter.augmentStatsForPackageForUser(stats, 382 packageName, userHandle, callerHasStatsPermission); 383 }, "queryStatsForPackage"); 384 } 385 return translate(stats); 386 } 387 } 388 389 @Override queryStatsForUid(String volumeUuid, int uid, String callingPackage)390 public StorageStats queryStatsForUid(String volumeUuid, int uid, String callingPackage) { 391 final int userId = UserHandle.getUserId(uid); 392 final int appId = UserHandle.getAppId(uid); 393 394 if (userId != UserHandle.getCallingUserId()) { 395 mContext.enforceCallingOrSelfPermission( 396 android.Manifest.permission.INTERACT_ACROSS_USERS, TAG); 397 } 398 399 final boolean callerHasStatsPermission; 400 if (Binder.getCallingUid() == uid) { 401 // No permissions required when asking about themselves. We still check since it is 402 // needed later on but don't throw if caller doesn't have the permission. 403 callerHasStatsPermission = checkStatsPermission( 404 Binder.getCallingUid(), callingPackage, false) == null; 405 } else { 406 enforceStatsPermission(Binder.getCallingUid(), callingPackage); 407 callerHasStatsPermission = true; 408 } 409 410 final String[] packageNames = defeatNullable(mPackage.getPackagesForUid(uid)); 411 final long[] ceDataInodes = new long[packageNames.length]; 412 String[] codePaths = new String[0]; 413 414 for (int i = 0; i < packageNames.length; i++) { 415 try { 416 final ApplicationInfo appInfo = mPackage.getApplicationInfoAsUser(packageNames[i], 417 PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); 418 if (appInfo.isSystemApp() && !appInfo.isUpdatedSystemApp()) { 419 // We don't count code baked into system image 420 } else { 421 codePaths = ArrayUtils.appendElement(String.class, codePaths, 422 appInfo.getCodePath()); 423 } 424 } catch (NameNotFoundException e) { 425 throw new ParcelableException(e); 426 } 427 } 428 429 final PackageStats stats = new PackageStats(TAG); 430 try { 431 mInstaller.getAppSize(volumeUuid, packageNames, userId, getDefaultFlags(), 432 appId, ceDataInodes, codePaths, stats); 433 434 if (SystemProperties.getBoolean(PROP_VERIFY_STORAGE, false)) { 435 final PackageStats manualStats = new PackageStats(TAG); 436 mInstaller.getAppSize(volumeUuid, packageNames, userId, 0, 437 appId, ceDataInodes, codePaths, manualStats); 438 checkEquals("UID " + uid, manualStats, stats); 439 } 440 } catch (InstallerException e) { 441 throw new ParcelableException(new IOException(e.getMessage())); 442 } 443 444 if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) { 445 forEachStorageStatsAugmenter((storageStatsAugmenter) -> { 446 storageStatsAugmenter.augmentStatsForUid(stats, uid, callerHasStatsPermission); 447 }, "queryStatsForUid"); 448 } 449 return translate(stats); 450 } 451 452 @Override queryStatsForUser(String volumeUuid, int userId, String callingPackage)453 public StorageStats queryStatsForUser(String volumeUuid, int userId, String callingPackage) { 454 if (userId != UserHandle.getCallingUserId()) { 455 mContext.enforceCallingOrSelfPermission( 456 android.Manifest.permission.INTERACT_ACROSS_USERS, TAG); 457 } 458 459 // Always require permission to see user-level stats 460 enforceStatsPermission(Binder.getCallingUid(), callingPackage); 461 462 final int[] appIds = getAppIds(userId); 463 final PackageStats stats = new PackageStats(TAG); 464 try { 465 mInstaller.getUserSize(volumeUuid, userId, getDefaultFlags(), appIds, stats); 466 467 if (SystemProperties.getBoolean(PROP_VERIFY_STORAGE, false)) { 468 final PackageStats manualStats = new PackageStats(TAG); 469 mInstaller.getUserSize(volumeUuid, userId, 0, appIds, manualStats); 470 checkEquals("User " + userId, manualStats, stats); 471 } 472 } catch (InstallerException e) { 473 throw new ParcelableException(new IOException(e.getMessage())); 474 } 475 if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) { 476 UserHandle userHandle = UserHandle.of(userId); 477 forEachStorageStatsAugmenter((storageStatsAugmenter) -> { 478 storageStatsAugmenter.augmentStatsForUser(stats, userHandle); 479 }, "queryStatsForUser"); 480 } 481 return translate(stats); 482 } 483 484 @Override queryExternalStatsForUser(String volumeUuid, int userId, String callingPackage)485 public ExternalStorageStats queryExternalStatsForUser(String volumeUuid, int userId, 486 String callingPackage) { 487 if (userId != UserHandle.getCallingUserId()) { 488 mContext.enforceCallingOrSelfPermission( 489 android.Manifest.permission.INTERACT_ACROSS_USERS, TAG); 490 } 491 492 // Always require permission to see user-level stats 493 enforceStatsPermission(Binder.getCallingUid(), callingPackage); 494 495 final int[] appIds = getAppIds(userId); 496 final long[] stats; 497 try { 498 stats = mInstaller.getExternalSize(volumeUuid, userId, getDefaultFlags(), appIds); 499 500 if (SystemProperties.getBoolean(PROP_VERIFY_STORAGE, false)) { 501 final long[] manualStats = mInstaller.getExternalSize(volumeUuid, userId, 0, 502 appIds); 503 checkEquals("External " + userId, manualStats, stats); 504 } 505 } catch (InstallerException e) { 506 throw new ParcelableException(new IOException(e.getMessage())); 507 } 508 509 final ExternalStorageStats res = new ExternalStorageStats(); 510 res.totalBytes = stats[0]; 511 res.audioBytes = stats[1]; 512 res.videoBytes = stats[2]; 513 res.imageBytes = stats[3]; 514 res.appBytes = stats[4]; 515 res.obbBytes = stats[5]; 516 return res; 517 } 518 getAppIds(int userId)519 private int[] getAppIds(int userId) { 520 int[] appIds = null; 521 for (ApplicationInfo app : mPackage.getInstalledApplicationsAsUser( 522 PackageManager.MATCH_UNINSTALLED_PACKAGES, userId)) { 523 final int appId = UserHandle.getAppId(app.uid); 524 if (!ArrayUtils.contains(appIds, appId)) { 525 appIds = ArrayUtils.appendInt(appIds, appId); 526 } 527 } 528 return appIds; 529 } 530 getDefaultFlags()531 private static int getDefaultFlags() { 532 if (SystemProperties.getBoolean(PROP_DISABLE_QUOTA, false)) { 533 return 0; 534 } else { 535 return Installer.FLAG_USE_QUOTA; 536 } 537 } 538 checkEquals(String msg, long[] a, long[] b)539 private static void checkEquals(String msg, long[] a, long[] b) { 540 for (int i = 0; i < a.length; i++) { 541 checkEquals(msg + "[" + i + "]", a[i], b[i]); 542 } 543 } 544 checkEquals(String msg, PackageStats a, PackageStats b)545 private static void checkEquals(String msg, PackageStats a, PackageStats b) { 546 checkEquals(msg + " codeSize", a.codeSize, b.codeSize); 547 checkEquals(msg + " dataSize", a.dataSize, b.dataSize); 548 checkEquals(msg + " cacheSize", a.cacheSize, b.cacheSize); 549 checkEquals(msg + " externalCodeSize", a.externalCodeSize, b.externalCodeSize); 550 checkEquals(msg + " externalDataSize", a.externalDataSize, b.externalDataSize); 551 checkEquals(msg + " externalCacheSize", a.externalCacheSize, b.externalCacheSize); 552 } 553 checkEquals(String msg, long expected, long actual)554 private static void checkEquals(String msg, long expected, long actual) { 555 if (expected != actual) { 556 Slog.e(TAG, msg + " expected " + expected + " actual " + actual); 557 } 558 } 559 translate(PackageStats stats)560 private static StorageStats translate(PackageStats stats) { 561 final StorageStats res = new StorageStats(); 562 res.codeBytes = stats.codeSize + stats.externalCodeSize; 563 res.dataBytes = stats.dataSize + stats.externalDataSize; 564 res.cacheBytes = stats.cacheSize + stats.externalCacheSize; 565 res.externalCacheBytes = stats.externalCacheSize; 566 return res; 567 } 568 569 private class H extends Handler { 570 private static final int MSG_CHECK_STORAGE_DELTA = 100; 571 private static final int MSG_LOAD_CACHED_QUOTAS_FROM_FILE = 101; 572 private static final int MSG_RECALCULATE_QUOTAS = 102; 573 private static final int MSG_PACKAGE_REMOVED = 103; 574 /** 575 * By only triggering a re-calculation after the storage has changed sizes, we can avoid 576 * recalculating quotas too often. Minimum change delta high and low define the 577 * percentage of change we need to see before we recalculate quotas when the device has 578 * enough storage space (more than mStorageThresholdPercentHigh of total 579 * free) and in low storage condition respectively. 580 */ 581 private static final long MINIMUM_CHANGE_DELTA_PERCENT_HIGH = 5; 582 private static final long MINIMUM_CHANGE_DELTA_PERCENT_LOW = 2; 583 private static final int UNSET = -1; 584 private static final boolean DEBUG = false; 585 586 private final StatFs mStats; 587 private long mPreviousBytes; 588 private long mTotalBytes; 589 H(Looper looper)590 public H(Looper looper) { 591 super(looper); 592 // TODO: Handle all private volumes. 593 mStats = new StatFs(Environment.getDataDirectory().getAbsolutePath()); 594 mPreviousBytes = mStats.getAvailableBytes(); 595 mTotalBytes = mStats.getTotalBytes(); 596 } 597 handleMessage(Message msg)598 public void handleMessage(Message msg) { 599 if (DEBUG) { 600 Slog.v(TAG, ">>> handling " + msg.what); 601 } 602 603 if (!isCacheQuotaCalculationsEnabled(mContext.getContentResolver())) { 604 return; 605 } 606 607 switch (msg.what) { 608 case MSG_CHECK_STORAGE_DELTA: { 609 mStats.restat(Environment.getDataDirectory().getAbsolutePath()); 610 long bytesDelta = Math.abs(mPreviousBytes - mStats.getAvailableBytes()); 611 long bytesDeltaThreshold; 612 synchronized (mLock) { 613 if (mStats.getAvailableBytes() > mTotalBytes 614 * mStorageThresholdPercentHigh / 100) { 615 bytesDeltaThreshold = mTotalBytes 616 * MINIMUM_CHANGE_DELTA_PERCENT_HIGH / 100; 617 } else { 618 bytesDeltaThreshold = mTotalBytes 619 * MINIMUM_CHANGE_DELTA_PERCENT_LOW / 100; 620 } 621 } 622 if (bytesDelta > bytesDeltaThreshold) { 623 mPreviousBytes = mStats.getAvailableBytes(); 624 recalculateQuotas(getInitializedStrategy()); 625 notifySignificantDelta(); 626 } 627 sendEmptyMessageDelayed(MSG_CHECK_STORAGE_DELTA, DELAY_CHECK_STORAGE_DELTA); 628 break; 629 } 630 case MSG_LOAD_CACHED_QUOTAS_FROM_FILE: { 631 CacheQuotaStrategy strategy = getInitializedStrategy(); 632 mPreviousBytes = UNSET; 633 try { 634 mPreviousBytes = strategy.setupQuotasFromFile(); 635 } catch (IOException e) { 636 Slog.e(TAG, "An error occurred while reading the cache quota file.", e); 637 } catch (IllegalStateException e) { 638 Slog.e(TAG, "Cache quota XML file is malformed?", e); 639 } 640 641 // If errors occurred getting the quotas from disk, let's re-calc them. 642 if (mPreviousBytes < 0) { 643 mStats.restat(Environment.getDataDirectory().getAbsolutePath()); 644 mPreviousBytes = mStats.getAvailableBytes(); 645 recalculateQuotas(strategy); 646 } 647 sendEmptyMessageDelayed(MSG_CHECK_STORAGE_DELTA, DELAY_CHECK_STORAGE_DELTA); 648 sendEmptyMessageDelayed(MSG_RECALCULATE_QUOTAS, DELAY_RECALCULATE_QUOTAS); 649 break; 650 } 651 case MSG_RECALCULATE_QUOTAS: { 652 recalculateQuotas(getInitializedStrategy()); 653 sendEmptyMessageDelayed(MSG_RECALCULATE_QUOTAS, DELAY_RECALCULATE_QUOTAS); 654 break; 655 } 656 case MSG_PACKAGE_REMOVED: { 657 // recalculate quotas when package is removed 658 recalculateQuotas(getInitializedStrategy()); 659 break; 660 } 661 default: 662 if (DEBUG) { 663 Slog.v(TAG, ">>> default message case "); 664 } 665 return; 666 } 667 } 668 recalculateQuotas(CacheQuotaStrategy strategy)669 private void recalculateQuotas(CacheQuotaStrategy strategy) { 670 if (DEBUG) { 671 Slog.v(TAG, ">>> recalculating quotas "); 672 } 673 674 strategy.recalculateQuotas(); 675 } 676 getInitializedStrategy()677 private CacheQuotaStrategy getInitializedStrategy() { 678 UsageStatsManagerInternal usageStatsManager = 679 LocalServices.getService(UsageStatsManagerInternal.class); 680 return new CacheQuotaStrategy(mContext, usageStatsManager, mInstaller, mCacheQuotas); 681 } 682 } 683 684 @VisibleForTesting isCacheQuotaCalculationsEnabled(ContentResolver resolver)685 static boolean isCacheQuotaCalculationsEnabled(ContentResolver resolver) { 686 return Settings.Global.getInt( 687 resolver, Settings.Global.ENABLE_CACHE_QUOTA_CALCULATION, 1) != 0; 688 } 689 690 /** 691 * Hacky way of notifying that disk space has changed significantly; we do 692 * this to cause "available space" values to be requeried. 693 */ notifySignificantDelta()694 void notifySignificantDelta() { 695 mContext.getContentResolver().notifyChange( 696 Uri.parse("content://com.android.externalstorage.documents/"), null, false); 697 } 698 checkCratesEnable()699 private static void checkCratesEnable() { 700 final boolean enable = SystemProperties.getBoolean(PROP_STORAGE_CRATES, false); 701 if (!enable) { 702 throw new IllegalStateException("Storage Crate feature is disabled."); 703 } 704 } 705 706 /** 707 * To enforce the calling or self to have the {@link android.Manifest.permission#MANAGE_CRATES} 708 * permission. 709 * @param callingUid the calling uid 710 * @param callingPackage the calling package name 711 */ enforceCratesPermission(int callingUid, String callingPackage)712 private void enforceCratesPermission(int callingUid, String callingPackage) { 713 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_CRATES, 714 callingPackage); 715 } 716 717 /** 718 * To copy from CrateMetadata instances into CrateInfo instances. 719 */ 720 @NonNull convertCrateInfoFrom(@ullable CrateMetadata[] crateMetadatas)721 private static List<CrateInfo> convertCrateInfoFrom(@Nullable CrateMetadata[] crateMetadatas) { 722 if (ArrayUtils.isEmpty(crateMetadatas)) { 723 return Collections.EMPTY_LIST; 724 } 725 726 ArrayList<CrateInfo> crateInfos = new ArrayList<>(); 727 for (CrateMetadata crateMetadata : crateMetadatas) { 728 if (crateMetadata == null || TextUtils.isEmpty(crateMetadata.id) 729 || TextUtils.isEmpty(crateMetadata.packageName)) { 730 continue; 731 } 732 733 CrateInfo crateInfo = CrateInfo.copyFrom(crateMetadata.uid, 734 crateMetadata.packageName, crateMetadata.id); 735 if (crateInfo == null) { 736 continue; 737 } 738 739 crateInfos.add(crateInfo); 740 } 741 742 return crateInfos; 743 } 744 745 @NonNull getAppCrates(String volumeUuid, String[] packageNames, @UserIdInt int userId)746 private ParceledListSlice<CrateInfo> getAppCrates(String volumeUuid, String[] packageNames, 747 @UserIdInt int userId) { 748 try { 749 CrateMetadata[] crateMetadatas = mInstaller.getAppCrates(volumeUuid, 750 packageNames, userId); 751 return new ParceledListSlice<>(convertCrateInfoFrom(crateMetadatas)); 752 } catch (InstallerException e) { 753 throw new ParcelableException(new IOException(e.getMessage())); 754 } 755 } 756 757 @NonNull 758 @Override queryCratesForPackage(String volumeUuid, @NonNull String packageName, @UserIdInt int userId, @NonNull String callingPackage)759 public ParceledListSlice<CrateInfo> queryCratesForPackage(String volumeUuid, 760 @NonNull String packageName, @UserIdInt int userId, @NonNull String callingPackage) { 761 checkCratesEnable(); 762 if (userId != UserHandle.getCallingUserId()) { 763 mContext.enforceCallingOrSelfPermission( 764 android.Manifest.permission.INTERACT_ACROSS_USERS, TAG); 765 } 766 767 final ApplicationInfo appInfo; 768 try { 769 appInfo = mPackage.getApplicationInfoAsUser(packageName, 770 PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); 771 } catch (NameNotFoundException e) { 772 throw new ParcelableException(e); 773 } 774 775 if (Binder.getCallingUid() == appInfo.uid) { 776 // No permissions required when asking about themselves 777 } else { 778 enforceCratesPermission(Binder.getCallingUid(), callingPackage); 779 } 780 781 final String[] packageNames = new String[] { packageName }; 782 return getAppCrates(volumeUuid, packageNames, userId); 783 } 784 785 @NonNull 786 @Override queryCratesForUid(String volumeUuid, int uid, @NonNull String callingPackage)787 public ParceledListSlice<CrateInfo> queryCratesForUid(String volumeUuid, int uid, 788 @NonNull String callingPackage) { 789 checkCratesEnable(); 790 final int userId = UserHandle.getUserId(uid); 791 if (userId != UserHandle.getCallingUserId()) { 792 mContext.enforceCallingOrSelfPermission( 793 android.Manifest.permission.INTERACT_ACROSS_USERS, TAG); 794 } 795 796 if (Binder.getCallingUid() == uid) { 797 // No permissions required when asking about themselves 798 } else { 799 enforceCratesPermission(Binder.getCallingUid(), callingPackage); 800 } 801 802 final String[] packageNames = defeatNullable(mPackage.getPackagesForUid(uid)); 803 String[] validatedPackageNames = new String[0]; 804 805 for (String packageName : packageNames) { 806 if (TextUtils.isEmpty(packageName)) { 807 continue; 808 } 809 810 try { 811 final ApplicationInfo appInfo = mPackage.getApplicationInfoAsUser(packageName, 812 PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); 813 if (appInfo == null) { 814 continue; 815 } 816 817 validatedPackageNames = ArrayUtils.appendElement(String.class, 818 validatedPackageNames, packageName); 819 } catch (NameNotFoundException e) { 820 throw new ParcelableException(e); 821 } 822 } 823 824 return getAppCrates(volumeUuid, validatedPackageNames, userId); 825 } 826 827 @NonNull 828 @Override queryCratesForUser(String volumeUuid, int userId, @NonNull String callingPackage)829 public ParceledListSlice<CrateInfo> queryCratesForUser(String volumeUuid, int userId, 830 @NonNull String callingPackage) { 831 checkCratesEnable(); 832 if (userId != UserHandle.getCallingUserId()) { 833 mContext.enforceCallingOrSelfPermission( 834 android.Manifest.permission.INTERACT_ACROSS_USERS, TAG); 835 } 836 837 // Always require permission to see user-level stats 838 enforceCratesPermission(Binder.getCallingUid(), callingPackage); 839 840 try { 841 CrateMetadata[] crateMetadatas = mInstaller.getUserCrates(volumeUuid, userId); 842 return new ParceledListSlice<>(convertCrateInfoFrom(crateMetadatas)); 843 } catch (InstallerException e) { 844 throw new ParcelableException(new IOException(e.getMessage())); 845 } 846 } 847 forEachStorageStatsAugmenter(@onNull Consumer<StorageStatsAugmenter> consumer, @NonNull String queryTag)848 void forEachStorageStatsAugmenter(@NonNull Consumer<StorageStatsAugmenter> consumer, 849 @NonNull String queryTag) { 850 for (int i = 0, count = mStorageStatsAugmenters.size(); i < count; ++i) { 851 final Pair<String, StorageStatsAugmenter> pair = mStorageStatsAugmenters.get(i); 852 final String augmenterTag = pair.first; 853 final StorageStatsAugmenter storageStatsAugmenter = pair.second; 854 855 Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, queryTag + ":" + augmenterTag); 856 try { 857 consumer.accept(storageStatsAugmenter); 858 } finally { 859 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); 860 } 861 } 862 } 863 864 private class LocalService implements StorageStatsManagerLocal { 865 @Override registerStorageStatsAugmenter( @onNull StorageStatsAugmenter storageStatsAugmenter, @NonNull String tag)866 public void registerStorageStatsAugmenter( 867 @NonNull StorageStatsAugmenter storageStatsAugmenter, 868 @NonNull String tag) { 869 mStorageStatsAugmenters.add(Pair.create(tag, storageStatsAugmenter)); 870 } 871 } 872 } 873