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