• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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