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