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
queryStatsForPackage(String volumeUuid, String packageName, int userId, String callingPackage)355     public StorageStats queryStatsForPackage(String volumeUuid, String packageName, int userId,
356             String callingPackage) {
357         if (userId != UserHandle.getCallingUserId()) {
358             mContext.enforceCallingOrSelfPermission(
359                     android.Manifest.permission.INTERACT_ACROSS_USERS, TAG);
360         }
361 
362         final ApplicationInfo appInfo;
363         try {
364             appInfo = mPackage.getApplicationInfoAsUser(packageName,
365                     PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
366         } catch (NameNotFoundException e) {
367             throw new ParcelableException(e);
368         }
369 
370         final boolean callerHasStatsPermission;
371         if (Binder.getCallingUid() == appInfo.uid) {
372             // No permissions required when asking about themselves. We still check since it is
373             // needed later on but don't throw if caller doesn't have the permission.
374             callerHasStatsPermission = checkStatsPermission(
375                     Binder.getCallingUid(), callingPackage, false) == null;
376         } else {
377             enforceStatsPermission(Binder.getCallingUid(), callingPackage);
378             callerHasStatsPermission = true;
379         }
380 
381         if (defeatNullable(mPackage.getPackagesForUid(appInfo.uid)).length == 1) {
382             // Only one package inside UID means we can fast-path
383             return queryStatsForUid(volumeUuid, appInfo.uid, callingPackage);
384         } else {
385             // Multiple packages means we need to go manual
386             final int appId = UserHandle.getAppId(appInfo.uid);
387             final String[] packageNames = new String[] { packageName };
388             final long[] ceDataInodes = new long[1];
389             String[] codePaths = new String[0];
390 
391             if (appInfo.isSystemApp() && !appInfo.isUpdatedSystemApp()) {
392                 // We don't count code baked into system image
393             } else {
394                 if (appInfo.getCodePath() != null) {
395                     codePaths = ArrayUtils.appendElement(String.class, codePaths,
396                         appInfo.getCodePath());
397                 }
398             }
399 
400             final PackageStats stats = new PackageStats(TAG);
401             try {
402                 mInstaller.getAppSize(volumeUuid, packageNames, userId, 0,
403                         appId, ceDataInodes, codePaths, stats);
404             } catch (InstallerException e) {
405                 throw new ParcelableException(new IOException(e.getMessage()));
406             }
407             if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
408                 UserHandle userHandle = UserHandle.of(userId);
409                 forEachStorageStatsAugmenter((storageStatsAugmenter) -> {
410                     storageStatsAugmenter.augmentStatsForPackageForUser(stats,
411                             packageName, userHandle, callerHasStatsPermission);
412                 }, "queryStatsForPackage");
413             }
414             return translate(stats);
415         }
416     }
417 
418     @Override
queryStatsForUid(String volumeUuid, int uid, String callingPackage)419     public StorageStats queryStatsForUid(String volumeUuid, int uid, String callingPackage) {
420         final int userId = UserHandle.getUserId(uid);
421         final int appId = UserHandle.getAppId(uid);
422 
423         if (userId != UserHandle.getCallingUserId()) {
424             mContext.enforceCallingOrSelfPermission(
425                     android.Manifest.permission.INTERACT_ACROSS_USERS, TAG);
426         }
427 
428         final boolean callerHasStatsPermission;
429         if (Binder.getCallingUid() == uid) {
430             // No permissions required when asking about themselves. We still check since it is
431             // needed later on but don't throw if caller doesn't have the permission.
432             callerHasStatsPermission = checkStatsPermission(
433                     Binder.getCallingUid(), callingPackage, false) == null;
434         } else {
435             enforceStatsPermission(Binder.getCallingUid(), callingPackage);
436             callerHasStatsPermission = true;
437         }
438 
439         final String[] packageNames = defeatNullable(mPackage.getPackagesForUid(uid));
440         final long[] ceDataInodes = new long[packageNames.length];
441         String[] codePaths = new String[0];
442 
443         final PackageStats stats = new PackageStats(TAG);
444         for (int i = 0; i < packageNames.length; i++) {
445             try {
446                 final ApplicationInfo appInfo = mPackage.getApplicationInfoAsUser(packageNames[i],
447                         PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
448                 if (appInfo.isSystemApp() && !appInfo.isUpdatedSystemApp()) {
449                     // We don't count code baked into system image
450                 } else {
451                     if (appInfo.getCodePath() != null) {
452                         codePaths = ArrayUtils.appendElement(String.class, codePaths,
453                             appInfo.getCodePath());
454                     }
455                     if (Flags.getAppBytesByDataTypeApi()) {
456                         computeAppStatsByDataTypes(
457                             stats, appInfo.sourceDir, packageNames[i]);
458                     }
459                 }
460             } catch (NameNotFoundException e) {
461                 throw new ParcelableException(e);
462             }
463         }
464 
465         try {
466             mInstaller.getAppSize(volumeUuid, packageNames, userId, getDefaultFlags(),
467                     appId, ceDataInodes, codePaths, stats);
468 
469             if (SystemProperties.getBoolean(PROP_VERIFY_STORAGE, false)) {
470                 final PackageStats manualStats = new PackageStats(TAG);
471                 mInstaller.getAppSize(volumeUuid, packageNames, userId, 0,
472                         appId, ceDataInodes, codePaths, manualStats);
473                 checkEquals("UID " + uid, manualStats, stats);
474             }
475         } catch (InstallerException e) {
476             throw new ParcelableException(new IOException(e.getMessage()));
477         }
478 
479         if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
480             forEachStorageStatsAugmenter((storageStatsAugmenter) -> {
481                 storageStatsAugmenter.augmentStatsForUid(stats, uid, callerHasStatsPermission);
482             }, "queryStatsForUid");
483         }
484         return translate(stats);
485     }
486 
487     @Override
queryStatsForUser(String volumeUuid, int userId, String callingPackage)488     public StorageStats queryStatsForUser(String volumeUuid, int userId, String callingPackage) {
489         if (userId != UserHandle.getCallingUserId()) {
490             mContext.enforceCallingOrSelfPermission(
491                     android.Manifest.permission.INTERACT_ACROSS_USERS, TAG);
492         }
493 
494         // Always require permission to see user-level stats
495         enforceStatsPermission(Binder.getCallingUid(), callingPackage);
496 
497         final int[] appIds = getAppIds(userId);
498         final PackageStats stats = new PackageStats(TAG);
499         try {
500             mInstaller.getUserSize(volumeUuid, userId, getDefaultFlags(), appIds, stats);
501 
502             if (SystemProperties.getBoolean(PROP_VERIFY_STORAGE, false)) {
503                 final PackageStats manualStats = new PackageStats(TAG);
504                 mInstaller.getUserSize(volumeUuid, userId, 0, appIds, manualStats);
505                 checkEquals("User " + userId, manualStats, stats);
506             }
507         } catch (InstallerException e) {
508             throw new ParcelableException(new IOException(e.getMessage()));
509         }
510         if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
511             UserHandle userHandle = UserHandle.of(userId);
512             forEachStorageStatsAugmenter((storageStatsAugmenter) -> {
513                 storageStatsAugmenter.augmentStatsForUser(stats, userHandle);
514             }, "queryStatsForUser");
515         }
516         return translate(stats);
517     }
518 
519     @Override
queryExternalStatsForUser(String volumeUuid, int userId, String callingPackage)520     public ExternalStorageStats queryExternalStatsForUser(String volumeUuid, int userId,
521             String callingPackage) {
522         if (userId != UserHandle.getCallingUserId()) {
523             mContext.enforceCallingOrSelfPermission(
524                     android.Manifest.permission.INTERACT_ACROSS_USERS, TAG);
525         }
526 
527         // Always require permission to see user-level stats
528         enforceStatsPermission(Binder.getCallingUid(), callingPackage);
529 
530         final int[] appIds = getAppIds(userId);
531         final long[] stats;
532         try {
533             stats = mInstaller.getExternalSize(volumeUuid, userId, getDefaultFlags(), appIds);
534 
535             if (SystemProperties.getBoolean(PROP_VERIFY_STORAGE, false)) {
536                 final long[] manualStats = mInstaller.getExternalSize(volumeUuid, userId, 0,
537                         appIds);
538                 checkEquals("External " + userId, manualStats, stats);
539             }
540         } catch (InstallerException e) {
541             throw new ParcelableException(new IOException(e.getMessage()));
542         }
543 
544         final ExternalStorageStats res = new ExternalStorageStats();
545         res.totalBytes = stats[0];
546         res.audioBytes = stats[1];
547         res.videoBytes = stats[2];
548         res.imageBytes = stats[3];
549         res.appBytes = stats[4];
550         res.obbBytes = stats[5];
551         return res;
552     }
553 
getAppIds(int userId)554     private int[] getAppIds(int userId) {
555         int[] appIds = null;
556         for (ApplicationInfo app : mPackage.getInstalledApplicationsAsUser(
557                 PackageManager.MATCH_UNINSTALLED_PACKAGES, userId)) {
558             final int appId = UserHandle.getAppId(app.uid);
559             if (!ArrayUtils.contains(appIds, appId)) {
560                 appIds = ArrayUtils.appendInt(appIds, appId);
561             }
562         }
563         return appIds;
564     }
565 
getDefaultFlags()566     private static int getDefaultFlags() {
567         if (SystemProperties.getBoolean(PROP_DISABLE_QUOTA, false)) {
568             return 0;
569         } else {
570             return Installer.FLAG_USE_QUOTA;
571         }
572     }
573 
checkEquals(String msg, long[] a, long[] b)574     private static void checkEquals(String msg, long[] a, long[] b) {
575         for (int i = 0; i < a.length; i++) {
576             checkEquals(msg + "[" + i + "]", a[i], b[i]);
577         }
578     }
579 
checkEquals(String msg, PackageStats a, PackageStats b)580     private static void checkEquals(String msg, PackageStats a, PackageStats b) {
581         checkEquals(msg + " codeSize", a.codeSize, b.codeSize);
582         checkEquals(msg + " dataSize", a.dataSize, b.dataSize);
583         checkEquals(msg + " cacheSize", a.cacheSize, b.cacheSize);
584         checkEquals(msg + " externalCodeSize", a.externalCodeSize, b.externalCodeSize);
585         checkEquals(msg + " externalDataSize", a.externalDataSize, b.externalDataSize);
586         checkEquals(msg + " externalCacheSize", a.externalCacheSize, b.externalCacheSize);
587     }
588 
checkEquals(String msg, long expected, long actual)589     private static void checkEquals(String msg, long expected, long actual) {
590         if (expected != actual) {
591             Slog.e(TAG, msg + " expected " + expected + " actual " + actual);
592         }
593     }
594 
translate(PackageStats stats)595     private static StorageStats translate(PackageStats stats) {
596         final StorageStats res = new StorageStats();
597         res.codeBytes = stats.codeSize + stats.externalCodeSize;
598         res.dataBytes = stats.dataSize + stats.externalDataSize;
599         res.cacheBytes = stats.cacheSize + stats.externalCacheSize;
600         res.dexoptBytes = stats.dexoptSize;
601         res.curProfBytes = stats.curProfSize;
602         res.refProfBytes = stats.refProfSize;
603         res.apkBytes = stats.apkSize;
604         res.libBytes = stats.libSize;
605         res.dmBytes = stats.dmSize;
606         res.externalCacheBytes = stats.externalCacheSize;
607         return res;
608     }
609 
610     private class H extends Handler {
611         private static final int MSG_CHECK_STORAGE_DELTA = 100;
612         private static final int MSG_LOAD_CACHED_QUOTAS_FROM_FILE = 101;
613         private static final int MSG_RECALCULATE_QUOTAS = 102;
614         private static final int MSG_PACKAGE_REMOVED = 103;
615         /**
616          * By only triggering a re-calculation after the storage has changed sizes, we can avoid
617          * recalculating quotas too often. Minimum change delta high and low define the
618          * percentage of change we need to see before we recalculate quotas when the device has
619          * enough storage space (more than mStorageThresholdPercentHigh of total
620          * free) and in low storage condition respectively.
621          */
622         private static final long MINIMUM_CHANGE_DELTA_PERCENT_HIGH = 5;
623         private static final long MINIMUM_CHANGE_DELTA_PERCENT_LOW = 2;
624         private static final int UNSET = -1;
625         private static final boolean DEBUG = false;
626 
627         private final StatFs mStats;
628         private long mPreviousBytes;
629         private long mTotalBytes;
630 
H(Looper looper)631         public H(Looper looper) {
632             super(looper);
633             // TODO: Handle all private volumes.
634             mStats = new StatFs(Environment.getDataDirectory().getAbsolutePath());
635             mPreviousBytes = mStats.getAvailableBytes();
636             mTotalBytes = mStats.getTotalBytes();
637         }
638 
handleMessage(Message msg)639         public void handleMessage(Message msg) {
640             if (DEBUG) {
641                 Slog.v(TAG, ">>> handling " + msg.what);
642             }
643 
644             if (!isCacheQuotaCalculationsEnabled(mContext.getContentResolver())) {
645                 return;
646             }
647 
648             switch (msg.what) {
649                 case MSG_CHECK_STORAGE_DELTA: {
650                     mStats.restat(Environment.getDataDirectory().getAbsolutePath());
651                     long bytesDelta = Math.abs(mPreviousBytes - mStats.getAvailableBytes());
652                     long bytesDeltaThreshold;
653                     synchronized (mLock) {
654                         if (mStats.getAvailableBytes() >  mTotalBytes
655                                 * mStorageThresholdPercentHigh / 100) {
656                             bytesDeltaThreshold = mTotalBytes
657                                     * MINIMUM_CHANGE_DELTA_PERCENT_HIGH / 100;
658                         } else {
659                             bytesDeltaThreshold = mTotalBytes
660                                     * MINIMUM_CHANGE_DELTA_PERCENT_LOW / 100;
661                         }
662                     }
663                     if (bytesDelta > bytesDeltaThreshold) {
664                         mPreviousBytes = mStats.getAvailableBytes();
665                         recalculateQuotas(getInitializedStrategy());
666                         notifySignificantDelta();
667                     }
668                     sendEmptyMessageDelayed(MSG_CHECK_STORAGE_DELTA, DELAY_CHECK_STORAGE_DELTA);
669                     break;
670                 }
671                 case MSG_LOAD_CACHED_QUOTAS_FROM_FILE: {
672                     CacheQuotaStrategy strategy = getInitializedStrategy();
673                     mPreviousBytes = UNSET;
674                     try {
675                         mPreviousBytes = strategy.setupQuotasFromFile();
676                     } catch (IOException e) {
677                         Slog.e(TAG, "An error occurred while reading the cache quota file.", e);
678                     } catch (IllegalStateException e) {
679                         Slog.e(TAG, "Cache quota XML file is malformed?", e);
680                     }
681 
682                     // If errors occurred getting the quotas from disk, let's re-calc them.
683                     if (mPreviousBytes < 0) {
684                         mStats.restat(Environment.getDataDirectory().getAbsolutePath());
685                         mPreviousBytes = mStats.getAvailableBytes();
686                         recalculateQuotas(strategy);
687                     }
688                     sendEmptyMessageDelayed(MSG_CHECK_STORAGE_DELTA, DELAY_CHECK_STORAGE_DELTA);
689                     sendEmptyMessageDelayed(MSG_RECALCULATE_QUOTAS, DELAY_RECALCULATE_QUOTAS);
690                     break;
691                 }
692                 case MSG_RECALCULATE_QUOTAS: {
693                     recalculateQuotas(getInitializedStrategy());
694                     sendEmptyMessageDelayed(MSG_RECALCULATE_QUOTAS, DELAY_RECALCULATE_QUOTAS);
695                     break;
696                 }
697                 case MSG_PACKAGE_REMOVED: {
698                     // recalculate quotas when package is removed
699                     recalculateQuotas(getInitializedStrategy());
700                     break;
701                 }
702                 default:
703                     if (DEBUG) {
704                         Slog.v(TAG, ">>> default message case ");
705                     }
706                     return;
707             }
708         }
709 
recalculateQuotas(CacheQuotaStrategy strategy)710         private void recalculateQuotas(CacheQuotaStrategy strategy) {
711             if (DEBUG) {
712                 Slog.v(TAG, ">>> recalculating quotas ");
713             }
714 
715             strategy.recalculateQuotas();
716         }
717 
getInitializedStrategy()718         private CacheQuotaStrategy getInitializedStrategy() {
719             UsageStatsManagerInternal usageStatsManager =
720                     LocalServices.getService(UsageStatsManagerInternal.class);
721             return new CacheQuotaStrategy(mContext, usageStatsManager, mInstaller, mCacheQuotas);
722         }
723     }
724 
725     @VisibleForTesting
isCacheQuotaCalculationsEnabled(ContentResolver resolver)726     static boolean isCacheQuotaCalculationsEnabled(ContentResolver resolver) {
727         return Settings.Global.getInt(
728                 resolver, Settings.Global.ENABLE_CACHE_QUOTA_CALCULATION, 1) != 0;
729     }
730 
731     /**
732      * Hacky way of notifying that disk space has changed significantly; we do
733      * this to cause "available space" values to be requeried.
734      */
notifySignificantDelta()735     void notifySignificantDelta() {
736         mContext.getContentResolver().notifyChange(
737                 Uri.parse("content://com.android.externalstorage.documents/"), null, false);
738     }
739 
checkCratesEnable()740     private static void checkCratesEnable() {
741         final boolean enable = SystemProperties.getBoolean(PROP_STORAGE_CRATES, false);
742         if (!enable) {
743             throw new IllegalStateException("Storage Crate feature is disabled.");
744         }
745     }
746 
747     /**
748      * To enforce the calling or self to have the {@link android.Manifest.permission#MANAGE_CRATES}
749      * permission.
750      * @param callingUid the calling uid
751      * @param callingPackage the calling package name
752      */
enforceCratesPermission(int callingUid, String callingPackage)753     private void enforceCratesPermission(int callingUid, String callingPackage) {
754         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_CRATES,
755                 callingPackage);
756     }
757 
758     /**
759      * To copy from CrateMetadata instances into CrateInfo instances.
760      */
761     @NonNull
convertCrateInfoFrom(@ullable CrateMetadata[] crateMetadatas)762     private static List<CrateInfo> convertCrateInfoFrom(@Nullable CrateMetadata[] crateMetadatas) {
763         if (ArrayUtils.isEmpty(crateMetadatas)) {
764             return Collections.EMPTY_LIST;
765         }
766 
767         ArrayList<CrateInfo> crateInfos = new ArrayList<>();
768         for (CrateMetadata crateMetadata : crateMetadatas) {
769             if (crateMetadata == null || TextUtils.isEmpty(crateMetadata.id)
770                     || TextUtils.isEmpty(crateMetadata.packageName)) {
771                 continue;
772             }
773 
774             CrateInfo crateInfo = CrateInfo.copyFrom(crateMetadata.uid,
775                     crateMetadata.packageName, crateMetadata.id);
776             if (crateInfo == null) {
777                 continue;
778             }
779 
780             crateInfos.add(crateInfo);
781         }
782 
783         return crateInfos;
784     }
785 
786     @NonNull
getAppCrates(String volumeUuid, String[] packageNames, @UserIdInt int userId)787     private ParceledListSlice<CrateInfo> getAppCrates(String volumeUuid, String[] packageNames,
788             @UserIdInt int userId) {
789         try {
790             CrateMetadata[] crateMetadatas = mInstaller.getAppCrates(volumeUuid,
791                     packageNames, userId);
792             return new ParceledListSlice<>(convertCrateInfoFrom(crateMetadatas));
793         } catch (InstallerException e) {
794             throw new ParcelableException(new IOException(e.getMessage()));
795         }
796     }
797 
798     @NonNull
799     @Override
queryCratesForPackage(String volumeUuid, @NonNull String packageName, @UserIdInt int userId, @NonNull String callingPackage)800     public ParceledListSlice<CrateInfo> queryCratesForPackage(String volumeUuid,
801             @NonNull String packageName, @UserIdInt int userId, @NonNull String callingPackage) {
802         checkCratesEnable();
803         if (userId != UserHandle.getCallingUserId()) {
804             mContext.enforceCallingOrSelfPermission(
805                     android.Manifest.permission.INTERACT_ACROSS_USERS, TAG);
806         }
807 
808         final ApplicationInfo appInfo;
809         try {
810             appInfo = mPackage.getApplicationInfoAsUser(packageName,
811                     PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
812         } catch (NameNotFoundException e) {
813             throw new ParcelableException(e);
814         }
815 
816         if (Binder.getCallingUid() == appInfo.uid) {
817             // No permissions required when asking about themselves
818         } else {
819             enforceCratesPermission(Binder.getCallingUid(), callingPackage);
820         }
821 
822         final String[] packageNames = new String[] { packageName };
823         return getAppCrates(volumeUuid, packageNames, userId);
824     }
825 
826     @NonNull
827     @Override
queryCratesForUid(String volumeUuid, int uid, @NonNull String callingPackage)828     public ParceledListSlice<CrateInfo> queryCratesForUid(String volumeUuid, int uid,
829             @NonNull String callingPackage) {
830         checkCratesEnable();
831         final int userId = UserHandle.getUserId(uid);
832         if (userId != UserHandle.getCallingUserId()) {
833             mContext.enforceCallingOrSelfPermission(
834                     android.Manifest.permission.INTERACT_ACROSS_USERS, TAG);
835         }
836 
837         if (Binder.getCallingUid() == uid) {
838             // No permissions required when asking about themselves
839         } else {
840             enforceCratesPermission(Binder.getCallingUid(), callingPackage);
841         }
842 
843         final String[] packageNames = defeatNullable(mPackage.getPackagesForUid(uid));
844         String[] validatedPackageNames = new String[0];
845 
846         for (String packageName : packageNames) {
847             if (TextUtils.isEmpty(packageName)) {
848                 continue;
849             }
850 
851             try {
852                 final ApplicationInfo appInfo = mPackage.getApplicationInfoAsUser(packageName,
853                         PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
854                 if (appInfo == null) {
855                     continue;
856                 }
857 
858                 validatedPackageNames = ArrayUtils.appendElement(String.class,
859                         validatedPackageNames, packageName);
860             } catch (NameNotFoundException e) {
861                 throw new ParcelableException(e);
862             }
863         }
864 
865         return getAppCrates(volumeUuid, validatedPackageNames, userId);
866     }
867 
868     @NonNull
869     @Override
queryCratesForUser(String volumeUuid, int userId, @NonNull String callingPackage)870     public ParceledListSlice<CrateInfo> queryCratesForUser(String volumeUuid, int userId,
871             @NonNull String callingPackage) {
872         checkCratesEnable();
873         if (userId != UserHandle.getCallingUserId()) {
874             mContext.enforceCallingOrSelfPermission(
875                     android.Manifest.permission.INTERACT_ACROSS_USERS, TAG);
876         }
877 
878         // Always require permission to see user-level stats
879         enforceCratesPermission(Binder.getCallingUid(), callingPackage);
880 
881         try {
882             CrateMetadata[] crateMetadatas = mInstaller.getUserCrates(volumeUuid, userId);
883             return new ParceledListSlice<>(convertCrateInfoFrom(crateMetadatas));
884         } catch (InstallerException e) {
885             throw new ParcelableException(new IOException(e.getMessage()));
886         }
887     }
888 
forEachStorageStatsAugmenter(@onNull Consumer<StorageStatsAugmenter> consumer, @NonNull String queryTag)889     void forEachStorageStatsAugmenter(@NonNull Consumer<StorageStatsAugmenter> consumer,
890                 @NonNull String queryTag) {
891         for (int i = 0, count = mStorageStatsAugmenters.size(); i < count; ++i) {
892             final Pair<String, StorageStatsAugmenter> pair = mStorageStatsAugmenters.get(i);
893             final String augmenterTag = pair.first;
894             final StorageStatsAugmenter storageStatsAugmenter = pair.second;
895 
896             Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, queryTag + ":" + augmenterTag);
897             try {
898                 consumer.accept(storageStatsAugmenter);
899             } finally {
900                 Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
901             }
902         }
903     }
904 
905     private class LocalService implements StorageStatsManagerLocal {
906         @Override
registerStorageStatsAugmenter( @onNull StorageStatsAugmenter storageStatsAugmenter, @NonNull String tag)907         public void registerStorageStatsAugmenter(
908                 @NonNull StorageStatsAugmenter storageStatsAugmenter,
909                 @NonNull String tag) {
910             mStorageStatsAugmenters.add(Pair.create(tag, storageStatsAugmenter));
911         }
912     }
913 
getDirBytes(File dir)914     private long getDirBytes(File dir) {
915         if (!dir.isDirectory()) {
916             return 0;
917         }
918 
919         long size = 0;
920         try {
921             for (File file : dir.listFiles()) {
922                 if (file.isFile()) {
923                     size += file.length();
924                     continue;
925                 }
926                 if (file.isDirectory()) {
927                     size += getDirBytes(file);
928                 }
929             }
930         } catch (NullPointerException e) {
931             Slog.w(TAG, "Failed to list directory " + dir.getName());
932         }
933 
934         return size;
935     }
936 
getFileBytesInDir(File dir, String suffix)937     private long getFileBytesInDir(File dir, String suffix) {
938         if (!dir.isDirectory()) {
939             return 0;
940         }
941 
942         long size = 0;
943         try {
944             for (File file : dir.listFiles()) {
945                 if (file.isFile() && file.getName().endsWith(suffix)) {
946                     size += file.length();
947                 }
948             }
949         } catch (NullPointerException e) {
950              Slog.w(TAG, "Failed to list directory " + dir.getName());
951         }
952 
953         return size;
954     }
955 
computeAppStatsByDataTypes( PackageStats stats, String sourceDirName, String packageName)956     private void computeAppStatsByDataTypes(
957         PackageStats stats, String sourceDirName, String packageName) {
958 
959         // Get apk, lib, dm file sizes.
960         File srcDir = new File(sourceDirName);
961         if (srcDir.isFile()) {
962             sourceDirName = srcDir.getParent();
963             srcDir = new File(sourceDirName);
964         }
965 
966         stats.apkSize += getFileBytesInDir(srcDir, ".apk");
967         stats.dmSize += getFileBytesInDir(srcDir, ".dm");
968         stats.libSize += getDirBytes(new File(sourceDirName + "/lib/"));
969 
970         // Get dexopt, current profle and reference profile sizes.
971         ArtManagedFileStats artManagedFileStats;
972         try (var snapshot = getPackageManagerLocal().withFilteredSnapshot()) {
973             artManagedFileStats =
974                 getArtManagerLocal().getArtManagedFileStats(snapshot, packageName);
975         }
976 
977         stats.dexoptSize +=
978             artManagedFileStats
979                 .getTotalSizeBytesByType(ArtManagedFileStats.TYPE_DEXOPT_ARTIFACT);
980         stats.refProfSize +=
981             artManagedFileStats
982                 .getTotalSizeBytesByType(ArtManagedFileStats.TYPE_REF_PROFILE);
983         stats.curProfSize +=
984             artManagedFileStats
985                 .getTotalSizeBytesByType(ArtManagedFileStats.TYPE_CUR_PROFILE);
986     }
987 }
988