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