diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java index b60aed68ba1ca..d71573f7ca50a 100644 --- a/cmds/pm/src/com/android/commands/pm/Pm.java +++ b/cmds/pm/src/com/android/commands/pm/Pm.java @@ -63,6 +63,7 @@ import android.os.ShellCallback; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; +import android.os.storage.StorageManager; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.Log; @@ -1471,7 +1472,8 @@ public final class Pm { } ClearDataObserver obs = new ClearDataObserver(); try { - mPm.freeStorageAndNotify(volumeUuid, sizeVal, obs); + mPm.freeStorageAndNotify(volumeUuid, sizeVal, + StorageManager.FLAG_ALLOCATE_DEFY_RESERVED, obs); synchronized (obs) { while (!obs.finished) { try { diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 525b15113855a..e5c420876c8a4 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -2108,7 +2108,7 @@ public class ApplicationPackageManager extends PackageManager { public void freeStorageAndNotify(String volumeUuid, long idealStorageSize, IPackageDataObserver observer) { try { - mPM.freeStorageAndNotify(volumeUuid, idealStorageSize, observer); + mPM.freeStorageAndNotify(volumeUuid, idealStorageSize, 0, observer); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -2117,7 +2117,7 @@ public class ApplicationPackageManager extends PackageManager { @Override public void freeStorage(String volumeUuid, long freeStorageSize, IntentSender pi) { try { - mPM.freeStorage(volumeUuid, freeStorageSize, pi); + mPM.freeStorage(volumeUuid, freeStorageSize, 0, pi); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/usage/IStorageStatsManager.aidl b/core/java/android/app/usage/IStorageStatsManager.aidl index 5d1550f1a5570..15e5ea5f44fff 100644 --- a/core/java/android/app/usage/IStorageStatsManager.aidl +++ b/core/java/android/app/usage/IStorageStatsManager.aidl @@ -24,6 +24,7 @@ interface IStorageStatsManager { boolean isQuotaSupported(String volumeUuid, String callingPackage); long getTotalBytes(String volumeUuid, String callingPackage); long getFreeBytes(String volumeUuid, String callingPackage); + long getCacheBytes(String volumeUuid, String callingPackage); long getCacheQuotaBytes(String volumeUuid, int uid, String callingPackage); StorageStats queryStatsForPackage(String volumeUuid, String packageName, int userId, String callingPackage); StorageStats queryStatsForUid(String volumeUuid, int uid, String callingPackage); diff --git a/core/java/android/app/usage/StorageStatsManager.java b/core/java/android/app/usage/StorageStatsManager.java index d9d958c0653c1..0b2b1900c4e00 100644 --- a/core/java/android/app/usage/StorageStatsManager.java +++ b/core/java/android/app/usage/StorageStatsManager.java @@ -142,6 +142,24 @@ public class StorageStatsManager { return getFreeBytes(convert(uuid)); } + /** {@hide} */ + public @BytesLong long getCacheBytes(@NonNull UUID storageUuid) throws IOException { + try { + return mService.getCacheBytes(convert(storageUuid), mContext.getOpPackageName()); + } catch (ParcelableException e) { + e.maybeRethrow(IOException.class); + throw new RuntimeException(e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** {@hide} */ + @Deprecated + public long getCacheBytes(String uuid) throws IOException { + return getCacheBytes(convert(uuid)); + } + /** * Return storage statistics for a specific package on the requested storage * volume. diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 7aaf453e404d3..2ebfa8fb3ac21 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -360,7 +360,7 @@ interface IPackageManager { * the operation is completed */ void freeStorageAndNotify(in String volumeUuid, in long freeStorageSize, - IPackageDataObserver observer); + int storageFlags, IPackageDataObserver observer); /** * Free storage by deleting LRU sorted list of cache files across @@ -384,7 +384,7 @@ interface IPackageManager { * to indicate that no call back is desired. */ void freeStorage(in String volumeUuid, in long freeStorageSize, - in IntentSender pi); + int storageFlags, in IntentSender pi); /** * Delete all the cache files in an applications cache directory diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index f361c549f0167..d81ee4ef98437 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -1642,11 +1642,20 @@ public class StorageManager { */ @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) @SystemApi - public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; + public static final int FLAG_ALLOCATE_AGGRESSIVE = 1 << 0; + + /** + * Flag indicating that a disk space allocation request should defy any + * reserved disk space. + * + * @hide + */ + public static final int FLAG_ALLOCATE_DEFY_RESERVED = 1 << 1; /** @hide */ @IntDef(flag = true, value = { FLAG_ALLOCATE_AGGRESSIVE, + FLAG_ALLOCATE_DEFY_RESERVED, }) @Retention(RetentionPolicy.SOURCE) public @interface AllocateFlags {} diff --git a/core/tests/coretests/src/android/content/pm/AppCacheTest.java b/core/tests/coretests/src/android/content/pm/AppCacheTest.java index 15dbddff54df6..59aa50ae14453 100644 --- a/core/tests/coretests/src/android/content/pm/AppCacheTest.java +++ b/core/tests/coretests/src/android/content/pm/AppCacheTest.java @@ -492,7 +492,7 @@ public class AppCacheTest extends AndroidTestCase { PackageDataObserver observer = new PackageDataObserver(); //wait on observer synchronized(observer) { - getPm().freeStorageAndNotify(null, idealStorageSize, observer); + getPm().freeStorageAndNotify(null, idealStorageSize, 0, observer); long waitTime = 0; while(!observer.isDone() || (waitTime > MAX_WAIT_TIME)) { observer.wait(WAIT_TIME_INCR); @@ -517,7 +517,7 @@ public class AppCacheTest extends AndroidTestCase { try { // Spin lock waiting for call back synchronized(r) { - getPm().freeStorage(null, idealStorageSize, pi.getIntentSender()); + getPm().freeStorage(null, idealStorageSize, 0, pi.getIntentSender()); long waitTime = 0; while(!r.isDone() && (waitTime < MAX_WAIT_TIME)) { r.wait(WAIT_TIME_INCR); diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java b/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java index 34fdc9ddd99d4..a8f6f025b6d51 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java @@ -63,15 +63,17 @@ public class StorageStatsSource { public long audioBytes; public long videoBytes; public long imageBytes; + public long appBytes; /** Convenience method for testing. */ @VisibleForTesting public ExternalStorageStats( - long totalBytes, long audioBytes, long videoBytes, long imageBytes) { + long totalBytes, long audioBytes, long videoBytes, long imageBytes, long appBytes) { this.totalBytes = totalBytes; this.audioBytes = audioBytes; this.videoBytes = videoBytes; this.imageBytes = imageBytes; + this.appBytes = appBytes; } /** @@ -84,6 +86,7 @@ public class StorageStatsSource { audioBytes = stats.getAudioBytes(); videoBytes = stats.getVideoBytes(); imageBytes = stats.getImageBytes(); + appBytes = stats.getAppBytes(); } } diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags index 6502c012ef5b6..68f8c1bb182fe 100644 --- a/services/core/java/com/android/server/EventLogTags.logtags +++ b/services/core/java/com/android/server/EventLogTags.logtags @@ -41,14 +41,10 @@ option java_package com.android.server # --------------------------- # DeviceStorageMonitorService.java # --------------------------- -# The disk space free on the /data partition, in bytes -2744 free_storage_changed (data|2|2) -# Device low memory notification and disk space free on the /data partition, in bytes at that time -2745 low_storage (data|2|2) -# disk space free on the /data, /system, and /cache partitions in bytes -2746 free_storage_left (data|2|2),(system|2|2),(cache|2|2) -# file on cache partition was deleted +# File on cache partition was deleted 2748 cache_file_deleted (path|3) +# Storage volume state and usable space in bytes +2749 storage_state (uuid|3),(old_state|1),(new_state|1),(usable|2),(total|2) # --------------------------- diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index cffb158a3f21e..35b452a12e64e 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -3296,6 +3296,9 @@ class StorageManagerService extends IStorageManager.Stub final StorageManager storage = mContext.getSystemService(StorageManager.class); final StorageStatsManager stats = mContext.getSystemService(StorageStatsManager.class); + // Apps can't defy reserved space + flags &= ~StorageManager.FLAG_ALLOCATE_DEFY_RESERVED; + final boolean aggressive = (flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0; if (aggressive) { mContext.enforceCallingOrSelfPermission( @@ -3306,24 +3309,31 @@ class StorageManagerService extends IStorageManager.Stub try { // In general, apps can allocate as much space as they want, except // we never let them eat into either the minimum cache space or into - // the low disk warning space. + // the low disk warning space. To avoid user confusion, this logic + // should be kept in sync with getFreeBytes(). final File path = storage.findPathForUuid(volumeUuid); + + final long usable = path.getUsableSpace(); + final long lowReserved = storage.getStorageLowBytes(path); + final long fullReserved = storage.getStorageFullBytes(path); + if (stats.isQuotaSupported(volumeUuid)) { + final long cacheTotal = stats.getCacheBytes(volumeUuid); + final long cacheReserved = storage.getStorageCacheBytes(path); + final long cacheClearable = Math.max(0, cacheTotal - cacheReserved); + if (aggressive) { - return Math.max(0, - stats.getFreeBytes(volumeUuid) - storage.getStorageFullBytes(path)); + return Math.max(0, (usable + cacheTotal) - fullReserved); } else { - return Math.max(0, - stats.getFreeBytes(volumeUuid) - storage.getStorageLowBytes(path) - - storage.getStorageCacheBytes(path)); + return Math.max(0, (usable + cacheClearable) - lowReserved); } } else { // When we don't have fast quota information, we ignore cached // data and only consider unused bytes. if (aggressive) { - return Math.max(0, path.getUsableSpace() - storage.getStorageFullBytes(path)); + return Math.max(0, usable - fullReserved); } else { - return Math.max(0, path.getUsableSpace() - storage.getStorageLowBytes(path)); + return Math.max(0, usable - lowReserved); } } } catch (IOException e) { @@ -3337,6 +3347,9 @@ class StorageManagerService extends IStorageManager.Stub public void allocateBytes(String volumeUuid, long bytes, int flags) { final StorageManager storage = mContext.getSystemService(StorageManager.class); + // Apps can't defy reserved space + flags &= ~StorageManager.FLAG_ALLOCATE_DEFY_RESERVED; + // This method call will enforce FLAG_ALLOCATE_AGGRESSIVE permissions so // we don't have to enforce them locally final long allocatableBytes = getAllocatableBytes(volumeUuid, flags); @@ -3350,7 +3363,11 @@ class StorageManagerService extends IStorageManager.Stub // Free up enough disk space to satisfy both the requested allocation // and our low disk warning space. final File path = storage.findPathForUuid(volumeUuid); - bytes += storage.getStorageLowBytes(path); + if ((flags & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0) { + bytes += storage.getStorageFullBytes(path); + } else { + bytes += storage.getStorageLowBytes(path); + } mPms.freeStorage(volumeUuid, bytes, flags); } catch (IOException e) { diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index e6e461744094b..c95b5c557b32e 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -395,10 +395,11 @@ public class Installer extends SystemService { } } - public void freeCache(String uuid, long freeStorageSize, int flags) throws InstallerException { + public void freeCache(String uuid, long targetFreeBytes, long cacheReservedBytes, int flags) + throws InstallerException { if (!checkBeforeRemote()) return; try { - mInstalld.freeCache(uuid, freeStorageSize, flags); + mInstalld.freeCache(uuid, targetFreeBytes, cacheReservedBytes, flags); } catch (Exception e) { throw InstallerException.from(e); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 782325a941b90..d427e1681e8c1 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4155,13 +4155,13 @@ public class PackageManagerService extends IPackageManager.Stub @Override public void freeStorageAndNotify(final String volumeUuid, final long freeStorageSize, - final IPackageDataObserver observer) { + final int storageFlags, final IPackageDataObserver observer) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CLEAR_APP_CACHE, null); mHandler.post(() -> { boolean success = false; try { - freeStorage(volumeUuid, freeStorageSize, 0); + freeStorage(volumeUuid, freeStorageSize, storageFlags); success = true; } catch (IOException e) { Slog.w(TAG, e); @@ -4178,13 +4178,13 @@ public class PackageManagerService extends IPackageManager.Stub @Override public void freeStorage(final String volumeUuid, final long freeStorageSize, - final IntentSender pi) { + final int storageFlags, final IntentSender pi) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CLEAR_APP_CACHE, TAG); mHandler.post(() -> { boolean success = false; try { - freeStorage(volumeUuid, freeStorageSize, 0); + freeStorage(volumeUuid, freeStorageSize, storageFlags); success = true; } catch (IOException e) { Slog.w(TAG, e); @@ -4209,10 +4209,14 @@ public class PackageManagerService extends IPackageManager.Stub if (file.getUsableSpace() >= bytes) return; if (ENABLE_FREE_CACHE_V2) { - final boolean aggressive = (storageFlags - & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0; final boolean internalVolume = Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, volumeUuid); + final boolean aggressive = (storageFlags + & StorageManager.FLAG_ALLOCATE_AGGRESSIVE) != 0; + final boolean defyReserved = (storageFlags + & StorageManager.FLAG_ALLOCATE_DEFY_RESERVED) != 0; + final long reservedBytes = (aggressive || defyReserved) ? 0 + : storage.getStorageCacheBytes(file); // 1. Pre-flight to determine if we have any chance to succeed // 2. Consider preloaded data (after 1w honeymoon, unless aggressive) @@ -4230,7 +4234,8 @@ public class PackageManagerService extends IPackageManager.Stub // 4. Consider cached app data (above quotas) try { - mInstaller.freeCache(volumeUuid, bytes, Installer.FLAG_FREE_CACHE_V2); + mInstaller.freeCache(volumeUuid, bytes, reservedBytes, + Installer.FLAG_FREE_CACHE_V2); } catch (InstallerException ignored) { } if (file.getUsableSpace() >= bytes) return; @@ -4241,8 +4246,8 @@ public class PackageManagerService extends IPackageManager.Stub // 8. Consider cached app data (below quotas) try { - mInstaller.freeCache(volumeUuid, bytes, Installer.FLAG_FREE_CACHE_V2 - | Installer.FLAG_FREE_CACHE_V2_DEFY_QUOTA); + mInstaller.freeCache(volumeUuid, bytes, reservedBytes, + Installer.FLAG_FREE_CACHE_V2 | Installer.FLAG_FREE_CACHE_V2_DEFY_QUOTA); } catch (InstallerException ignored) { } if (file.getUsableSpace() >= bytes) return; @@ -4252,7 +4257,7 @@ public class PackageManagerService extends IPackageManager.Stub } else { try { - mInstaller.freeCache(volumeUuid, bytes, 0); + mInstaller.freeCache(volumeUuid, bytes, 0, 0); } catch (InstallerException ignored) { } if (file.getUsableSpace() >= bytes) return; @@ -15494,7 +15499,7 @@ public class PackageManagerService extends IPackageManager.Stub origin.resolvedPath, isForwardLocked(), packageAbiOverride); try { - mInstaller.freeCache(null, sizeBytes + lowThreshold, 0); + mInstaller.freeCache(null, sizeBytes + lowThreshold, 0, 0); pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags, packageAbiOverride); } catch (InstallerException e) { diff --git a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java index fbc9e56fabf56..88b6d870afd76 100644 --- a/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java +++ b/services/core/java/com/android/server/storage/DeviceStorageMonitorService.java @@ -16,71 +16,60 @@ package com.android.server.storage; -import android.app.NotificationChannel; - -import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; -import com.android.internal.notification.SystemNotificationChannels; -import com.android.internal.util.DumpUtils; -import com.android.server.EventLogTags; -import com.android.server.SystemService; -import com.android.server.pm.InstructionSets; +import android.annotation.WorkerThread; import android.app.Notification; +import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; -import android.content.pm.IPackageDataObserver; -import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.net.TrafficStats; import android.os.Binder; import android.os.Environment; import android.os.FileObserver; import android.os.Handler; import android.os.Message; -import android.os.RemoteException; import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.ShellCallback; import android.os.ShellCommand; -import android.os.StatFs; -import android.os.SystemClock; -import android.os.SystemProperties; import android.os.UserHandle; import android.os.storage.StorageManager; -import android.provider.Settings; -import android.text.format.Formatter; -import android.util.EventLog; +import android.os.storage.VolumeInfo; +import android.text.format.DateUtils; +import android.util.ArrayMap; import android.util.Slog; -import android.util.TimeUtils; -import java.io.File; -import java.io.FileDescriptor; -import java.io.PrintWriter; -import java.util.concurrent.atomic.AtomicInteger; +import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; +import com.android.internal.notification.SystemNotificationChannels; +import com.android.internal.util.DumpUtils; +import com.android.internal.util.IndentingPrintWriter; +import com.android.server.EventLogTags; +import com.android.server.IoThread; +import com.android.server.SystemService; +import com.android.server.pm.InstructionSets; +import com.android.server.pm.PackageManagerService; import dalvik.system.VMRuntime; +import java.io.File; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; + /** - * This class implements a service to monitor the amount of disk - * storage space on the device. If the free storage on device is less - * than a tunable threshold value (a secure settings parameter; - * default 10%) a low memory notification is displayed to alert the - * user. If the user clicks on the low memory notification the - * Application Manager application gets launched to let the user free - * storage space. - * - * Event log events: A low memory event with the free storage on - * device in bytes is logged to the event log when the device goes low - * on storage space. The amount of free storage on the device is - * periodically logged to the event log. The log interval is a secure - * settings parameter with a default value of 12 hours. When the free - * storage differential goes below a threshold (again a secure - * settings parameter with a default value of 2MB), the free memory is - * logged to the event log. + * Service that monitors and maintains free space on storage volumes. + *
+ * As the free space on a volume nears the threshold defined by
+ * {@link StorageManager#getStorageLowBytes(File)}, this service will clear out
+ * cached data to keep the disk from entering this low state.
*/
public class DeviceStorageMonitorService extends SystemService {
- static final String TAG = "DeviceStorageMonitorService";
+ private static final String TAG = "DeviceStorageMonitorService";
/**
* Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
@@ -88,68 +77,75 @@ public class DeviceStorageMonitorService extends SystemService {
*/
public static final String EXTRA_SEQUENCE = "seq";
- // TODO: extend to watch and manage caches on all private volumes
+ private static final int MSG_CHECK = 1;
- static final boolean DEBUG = false;
- static final boolean localLOGV = false;
-
- static final int DEVICE_MEMORY_WHAT = 1;
- static final int FORCE_MEMORY_WHAT = 2;
- private static final int MONITOR_INTERVAL = 1; //in minutes
-
- private static final int DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES = 12*60; //in minutes
- private static final long DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD = 2 * 1024 * 1024; // 2MB
- private static final long DEFAULT_CHECK_INTERVAL = MONITOR_INTERVAL*60*1000;
+ private static final long DEFAULT_LOG_DELTA_BYTES = 64 * TrafficStats.MB_IN_BYTES;
+ private static final long DEFAULT_CHECK_INTERVAL = DateUtils.MINUTE_IN_MILLIS;
// com.android.internal.R.string.low_internal_storage_view_text_no_boot
// hard codes 250MB in the message as the storage space required for the
// boot image.
- private static final long BOOT_IMAGE_STORAGE_REQUIREMENT = 250 * 1024 * 1024;
+ private static final long BOOT_IMAGE_STORAGE_REQUIREMENT = 250 * TrafficStats.MB_IN_BYTES;
- private long mFreeMem; // on /data
- private long mFreeMemAfterLastCacheClear; // on /data
- private long mLastReportedFreeMem;
- private long mLastReportedFreeMemTime;
- boolean mLowMemFlag=false;
- private boolean mMemFullFlag=false;
- private final boolean mIsBootImageOnDisk;
- private final ContentResolver mResolver;
- private final long mTotalMemory; // on /data
- private final StatFs mDataFileStats;
- private final StatFs mSystemFileStats;
- private final StatFs mCacheFileStats;
+ private NotificationManager mNotifManager;
- private static final File DATA_PATH = Environment.getDataDirectory();
- private static final File SYSTEM_PATH = Environment.getRootDirectory();
- private static final File CACHE_PATH = Environment.getDownloadCacheDirectory();
+ /** Sequence number used for testing */
+ private final AtomicInteger mSeq = new AtomicInteger(1);
+ /** Forced level used for testing */
+ private volatile int mForceLevel = State.LEVEL_UNKNOWN;
+
+ /** Map from storage volume UUID to internal state */
+ private final ArrayMap
+ * As an example, a transition from {@link #LEVEL_NORMAL} to
+ * {@link #LEVEL_FULL} is considered to "enter" both {@link #LEVEL_LOW}
+ * and {@link #LEVEL_FULL}.
+ */
+ private static boolean isEntering(int level, int oldLevel, int newLevel) {
+ return newLevel >= level && (oldLevel < level || oldLevel == LEVEL_UNKNOWN);
+ }
+
+ /**
+ * Test if the given level transition is "leaving" a specific level.
+ *
+ * As an example, a transition from {@link #LEVEL_FULL} to
+ * {@link #LEVEL_NORMAL} is considered to "leave" both
+ * {@link #LEVEL_FULL} and {@link #LEVEL_LOW}.
+ */
+ private static boolean isLeaving(int level, int oldLevel, int newLevel) {
+ return newLevel < level && (oldLevel >= level || oldLevel == LEVEL_UNKNOWN);
+ }
+
+ private static String levelToString(int level) {
+ switch (level) {
+ case State.LEVEL_UNKNOWN: return "UNKNOWN";
+ case State.LEVEL_NORMAL: return "NORMAL";
+ case State.LEVEL_LOW: return "LOW";
+ case State.LEVEL_FULL: return "FULL";
+ default: return Integer.toString(level);
+ }
+ }
+ }
- private long mThreadStartTime = -1;
- boolean mUpdatesStopped;
- AtomicInteger mSeq = new AtomicInteger(1);
- boolean mClearSucceeded = false;
- boolean mClearingCache;
- private final Intent mStorageLowIntent;
- private final Intent mStorageOkIntent;
- private final Intent mStorageFullIntent;
- private final Intent mStorageNotFullIntent;
- private CachePackageDataObserver mClearCacheObserver;
private CacheFileDeletedObserver mCacheFileDeletedObserver;
- private static final int _TRUE = 1;
- private static final int _FALSE = 0;
- // This is the raw threshold that has been set at which we consider
- // storage to be low.
- long mMemLowThreshold;
- // This is the threshold at which we start trying to flush caches
- // to get below the low threshold limit. It is less than the low
- // threshold; we will allow storage to get a bit beyond the limit
- // before flushing and checking if we are actually low.
- private long mMemCacheStartTrimThreshold;
- // This is the threshold that we try to get to when deleting cache
- // files. This is greater than the low threshold so that we will flush
- // more files than absolutely needed, to reduce the frequency that
- // flushing takes place.
- private long mMemCacheTrimToThreshold;
- private long mMemFullThreshold;
/**
* This string is used for ServiceManager access to this class.
@@ -159,244 +155,107 @@ public class DeviceStorageMonitorService extends SystemService {
private static final String TV_NOTIFICATION_CHANNEL_ID = "devicestoragemonitor.tv";
/**
- * Handler that checks the amount of disk space on the device and sends a
- * notification if the device runs low on disk space
- */
- private final Handler mHandler = new Handler() {
+ * Handler that checks the amount of disk space on the device and sends a
+ * notification if the device runs low on disk space
+ */
+ private final Handler mHandler = new Handler(IoThread.get().getLooper()) {
@Override
public void handleMessage(Message msg) {
- //don't handle an invalid message
switch (msg.what) {
- case DEVICE_MEMORY_WHAT:
- checkMemory(msg.arg1 == _TRUE);
- return;
- case FORCE_MEMORY_WHAT:
- forceMemory(msg.arg1, msg.arg2);
- return;
- default:
- Slog.w(TAG, "Will not process invalid message");
+ case MSG_CHECK:
+ check();
return;
}
}
};
- private class CachePackageDataObserver extends IPackageDataObserver.Stub {
- public void onRemoveCompleted(String packageName, boolean succeeded) {
- mClearSucceeded = succeeded;
- mClearingCache = false;
- if(localLOGV) Slog.i(TAG, " Clear succeeded:"+mClearSucceeded
- +", mClearingCache:"+mClearingCache+" Forcing memory check");
- postCheckMemoryMsg(false, 0);
+ private State findOrCreateState(UUID uuid) {
+ State state = mStates.get(uuid);
+ if (state == null) {
+ state = new State();
+ mStates.put(uuid, state);
}
+ return state;
}
- private void restatDataDir() {
- try {
- mDataFileStats.restat(DATA_PATH.getAbsolutePath());
- mFreeMem = (long) mDataFileStats.getAvailableBlocks() *
- mDataFileStats.getBlockSize();
- } catch (IllegalArgumentException e) {
- // use the old value of mFreeMem
- }
- // Allow freemem to be overridden by debug.freemem for testing
- String debugFreeMem = SystemProperties.get("debug.freemem");
- if (!"".equals(debugFreeMem)) {
- mFreeMem = Long.parseLong(debugFreeMem);
- }
- // Read the log interval from secure settings
- long freeMemLogInterval = Settings.Global.getLong(mResolver,
- Settings.Global.SYS_FREE_STORAGE_LOG_INTERVAL,
- DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES)*60*1000;
- //log the amount of free memory in event log
- long currTime = SystemClock.elapsedRealtime();
- if((mLastReportedFreeMemTime == 0) ||
- (currTime-mLastReportedFreeMemTime) >= freeMemLogInterval) {
- mLastReportedFreeMemTime = currTime;
- long mFreeSystem = -1, mFreeCache = -1;
- try {
- mSystemFileStats.restat(SYSTEM_PATH.getAbsolutePath());
- mFreeSystem = (long) mSystemFileStats.getAvailableBlocks() *
- mSystemFileStats.getBlockSize();
- } catch (IllegalArgumentException e) {
- // ignore; report -1
- }
- try {
- mCacheFileStats.restat(CACHE_PATH.getAbsolutePath());
- mFreeCache = (long) mCacheFileStats.getAvailableBlocks() *
- mCacheFileStats.getBlockSize();
- } catch (IllegalArgumentException e) {
- // ignore; report -1
- }
- EventLog.writeEvent(EventLogTags.FREE_STORAGE_LEFT,
- mFreeMem, mFreeSystem, mFreeCache);
- }
- // Read the reporting threshold from secure settings
- long threshold = Settings.Global.getLong(mResolver,
- Settings.Global.DISK_FREE_CHANGE_REPORTING_THRESHOLD,
- DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD);
- // If mFree changed significantly log the new value
- long delta = mFreeMem - mLastReportedFreeMem;
- if (delta > threshold || delta < -threshold) {
- mLastReportedFreeMem = mFreeMem;
- EventLog.writeEvent(EventLogTags.FREE_STORAGE_CHANGED, mFreeMem);
- }
- }
+ /**
+ * Core logic that checks the storage state of every mounted private volume.
+ * Since this can do heavy I/O, callers should invoke indirectly using
+ * {@link #MSG_CHECK}.
+ */
+ @WorkerThread
+ private void check() {
+ final StorageManager storage = getContext().getSystemService(StorageManager.class);
+ final int seq = mSeq.get();
- private void clearCache() {
- if (mClearCacheObserver == null) {
- // Lazy instantiation
- mClearCacheObserver = new CachePackageDataObserver();
- }
- mClearingCache = true;
- try {
- if (localLOGV) Slog.i(TAG, "Clearing cache");
- IPackageManager.Stub.asInterface(ServiceManager.getService("package")).
- freeStorageAndNotify(null, mMemCacheTrimToThreshold, mClearCacheObserver);
- } catch (RemoteException e) {
- Slog.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
- mClearingCache = false;
- mClearSucceeded = false;
- }
- }
+ // Check every mounted private volume to see if they're low on space
+ for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
+ final File file = vol.getPath();
+ final long fullBytes = storage.getStorageFullBytes(file);
+ final long lowBytes = storage.getStorageLowBytes(file);
- void forceMemory(int opts, int seq) {
- if ((opts&OPTION_UPDATES_STOPPED) == 0) {
- if (mUpdatesStopped) {
- mUpdatesStopped = false;
- checkMemory(true);
- }
- } else {
- mUpdatesStopped = true;
- final boolean forceLow = (opts&OPTION_STORAGE_LOW) != 0;
- if (mLowMemFlag != forceLow || (opts&OPTION_FORCE_UPDATE) != 0) {
- mLowMemFlag = forceLow;
- if (forceLow) {
- sendNotification(seq);
- } else {
- cancelNotification(seq);
+ // Automatically trim cached data when nearing the low threshold;
+ // when it's within 150% of the threshold, we try trimming usage
+ // back to 200% of the threshold.
+ if (file.getUsableSpace() < (lowBytes * 3) / 2) {
+ final PackageManagerService pms = (PackageManagerService) ServiceManager
+ .getService("package");
+ try {
+ pms.freeStorage(vol.getFsUuid(), lowBytes * 2, 0);
+ } catch (IOException e) {
+ Slog.w(TAG, e);
}
}
- }
- }
- void checkMemory(boolean checkCache) {
- if (mUpdatesStopped) {
- return;
- }
+ // Send relevant broadcasts and show notifications based on any
+ // recently noticed state transitions.
+ final UUID uuid = StorageManager.convert(vol.getFsUuid());
+ final State state = findOrCreateState(uuid);
+ final long totalBytes = file.getTotalSpace();
+ final long usableBytes = file.getUsableSpace();
- //if the thread that was started to clear cache is still running do nothing till its
- //finished clearing cache. Ideally this flag could be modified by clearCache
- // and should be accessed via a lock but even if it does this test will fail now and
- //hopefully the next time this flag will be set to the correct value.
- if (mClearingCache) {
- if(localLOGV) Slog.i(TAG, "Thread already running just skip");
- //make sure the thread is not hung for too long
- long diffTime = System.currentTimeMillis() - mThreadStartTime;
- if(diffTime > (10*60*1000)) {
- Slog.w(TAG, "Thread that clears cache file seems to run for ever");
- }
- } else {
- restatDataDir();
- if (localLOGV) Slog.v(TAG, "freeMemory="+mFreeMem);
-
- //post intent to NotificationManager to display icon if necessary
- if (mFreeMem < mMemLowThreshold) {
- if (checkCache) {
- // We are allowed to clear cache files at this point to
- // try to get down below the limit, because this is not
- // the initial call after a cache clear has been attempted.
- // In this case we will try a cache clear if our free
- // space has gone below the cache clear limit.
- if (mFreeMem < mMemCacheStartTrimThreshold) {
- // We only clear the cache if the free storage has changed
- // a significant amount since the last time.
- if ((mFreeMemAfterLastCacheClear-mFreeMem)
- >= ((mMemLowThreshold-mMemCacheStartTrimThreshold)/4)) {
- // See if clearing cache helps
- // Note that clearing cache is asynchronous and so we do a
- // memory check again once the cache has been cleared.
- mThreadStartTime = System.currentTimeMillis();
- mClearSucceeded = false;
- clearCache();
- }
- }
- } else {
- // This is a call from after clearing the cache. Note
- // the amount of free storage at this point.
- mFreeMemAfterLastCacheClear = mFreeMem;
- if (!mLowMemFlag) {
- // We tried to clear the cache, but that didn't get us
- // below the low storage limit. Tell the user.
- Slog.i(TAG, "Running low on memory. Sending notification");
- sendNotification(0);
- mLowMemFlag = true;
- } else {
- if (localLOGV) Slog.v(TAG, "Running low on memory " +
- "notification already sent. do nothing");
- }
- }
+ int oldLevel = state.level;
+ int newLevel;
+ if (mForceLevel != State.LEVEL_UNKNOWN) {
+ // When in testing mode, use unknown old level to force sending
+ // of any relevant broadcasts.
+ oldLevel = State.LEVEL_UNKNOWN;
+ newLevel = mForceLevel;
+ } else if (usableBytes <= fullBytes) {
+ newLevel = State.LEVEL_FULL;
+ } else if (usableBytes <= lowBytes) {
+ newLevel = State.LEVEL_LOW;
+ } else if (StorageManager.UUID_DEFAULT.equals(uuid) && !isBootImageOnDisk()
+ && usableBytes < BOOT_IMAGE_STORAGE_REQUIREMENT) {
+ newLevel = State.LEVEL_LOW;
} else {
- mFreeMemAfterLastCacheClear = mFreeMem;
- if (mLowMemFlag) {
- Slog.i(TAG, "Memory available. Cancelling notification");
- cancelNotification(0);
- mLowMemFlag = false;
- }
+ newLevel = State.LEVEL_NORMAL;
}
- if (!mLowMemFlag && !mIsBootImageOnDisk && mFreeMem < BOOT_IMAGE_STORAGE_REQUIREMENT) {
- Slog.i(TAG, "No boot image on disk due to lack of space. Sending notification");
- sendNotification(0);
- mLowMemFlag = true;
- }
- if (mFreeMem < mMemFullThreshold) {
- if (!mMemFullFlag) {
- sendFullNotification();
- mMemFullFlag = true;
- }
- } else {
- if (mMemFullFlag) {
- cancelFullNotification();
- mMemFullFlag = false;
- }
- }
- }
- if(localLOGV) Slog.i(TAG, "Posting Message again");
- //keep posting messages to itself periodically
- postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL);
- }
- void postCheckMemoryMsg(boolean clearCache, long delay) {
- // Remove queued messages
- mHandler.removeMessages(DEVICE_MEMORY_WHAT);
- mHandler.sendMessageDelayed(mHandler.obtainMessage(DEVICE_MEMORY_WHAT,
- clearCache ?_TRUE : _FALSE, 0),
- delay);
+ // Log whenever we notice drastic storage changes
+ if ((Math.abs(state.lastUsableBytes - usableBytes) > DEFAULT_LOG_DELTA_BYTES)
+ || oldLevel != newLevel) {
+ EventLogTags.writeStorageState(uuid.toString(), oldLevel, newLevel,
+ usableBytes, totalBytes);
+ state.lastUsableBytes = usableBytes;
+ }
+
+ updateNotifications(vol, oldLevel, newLevel);
+ updateBroadcasts(vol, oldLevel, newLevel, seq);
+
+ state.level = newLevel;
+ }
+
+ // Loop around to check again in future; we don't remove messages since
+ // there might be an immediate request pending.
+ if (!mHandler.hasMessages(MSG_CHECK)) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHECK),
+ DEFAULT_CHECK_INTERVAL);
+ }
}
public DeviceStorageMonitorService(Context context) {
super(context);
- mLastReportedFreeMemTime = 0;
- mResolver = context.getContentResolver();
- mIsBootImageOnDisk = isBootImageOnDisk();
- //create StatFs object
- mDataFileStats = new StatFs(DATA_PATH.getAbsolutePath());
- mSystemFileStats = new StatFs(SYSTEM_PATH.getAbsolutePath());
- mCacheFileStats = new StatFs(CACHE_PATH.getAbsolutePath());
- //initialize total storage on device
- mTotalMemory = (long)mDataFileStats.getBlockCount() *
- mDataFileStats.getBlockSize();
- mStorageLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW);
- mStorageLowIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
- | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
- | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
- mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK);
- mStorageOkIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
- | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
- | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
- mStorageFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL);
- mStorageFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
- mStorageNotFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
- mStorageNotFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
}
private static boolean isBootImageOnDisk() {
@@ -408,35 +267,20 @@ public class DeviceStorageMonitorService extends SystemService {
return true;
}
- /**
- * Initializes the disk space threshold value and posts an empty message to
- * kickstart the process.
- */
@Override
public void onStart() {
- // cache storage thresholds
- Context context = getContext();
- final StorageManager sm = StorageManager.from(context);
- mMemLowThreshold = sm.getStorageLowBytes(DATA_PATH);
- mMemFullThreshold = sm.getStorageFullBytes(DATA_PATH);
-
- mMemCacheStartTrimThreshold = ((mMemLowThreshold*3)+mMemFullThreshold)/4;
- mMemCacheTrimToThreshold = mMemLowThreshold
- + ((mMemLowThreshold-mMemCacheStartTrimThreshold)*2);
- mFreeMemAfterLastCacheClear = mTotalMemory;
- checkMemory(true);
+ final Context context = getContext();
+ mNotifManager = context.getSystemService(NotificationManager.class);
mCacheFileDeletedObserver = new CacheFileDeletedObserver();
mCacheFileDeletedObserver.startWatching();
// Ensure that the notification channel is set up
- NotificationManager notificationMgr =
- (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
PackageManager packageManager = context.getPackageManager();
boolean isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
if (isTv) {
- notificationMgr.createNotificationChannel(new NotificationChannel(
+ mNotifManager.createNotificationChannel(new NotificationChannel(
TV_NOTIFICATION_CHANNEL_ID,
context.getString(
com.android.internal.R.string.device_storage_monitor_notification_channel),
@@ -445,23 +289,29 @@ public class DeviceStorageMonitorService extends SystemService {
publishBinderService(SERVICE, mRemoteService);
publishLocalService(DeviceStorageMonitorInternal.class, mLocalService);
+
+ // Kick off pass to examine storage state
+ mHandler.removeMessages(MSG_CHECK);
+ mHandler.obtainMessage(MSG_CHECK).sendToTarget();
}
private final DeviceStorageMonitorInternal mLocalService = new DeviceStorageMonitorInternal() {
@Override
public void checkMemory() {
- // force an early check
- postCheckMemoryMsg(true, 0);
+ // Kick off pass to examine storage state
+ mHandler.removeMessages(MSG_CHECK);
+ mHandler.obtainMessage(MSG_CHECK).sendToTarget();
}
@Override
public boolean isMemoryLow() {
- return mLowMemFlag;
+ return Environment.getDataDirectory().getUsableSpace() < getMemoryLowThreshold();
}
@Override
public long getMemoryLowThreshold() {
- return mMemLowThreshold;
+ return getContext().getSystemService(StorageManager.class)
+ .getStorageLowBytes(Environment.getDataDirectory());
}
};
@@ -494,8 +344,6 @@ public class DeviceStorageMonitorService extends SystemService {
}
static final int OPTION_FORCE_UPDATE = 1<<0;
- static final int OPTION_UPDATES_STOPPED = 1<<1;
- static final int OPTION_STORAGE_LOW = 1<<2;
int parseOptions(Shell shell) {
String opt;
@@ -518,10 +366,11 @@ public class DeviceStorageMonitorService extends SystemService {
int opts = parseOptions(shell);
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.DEVICE_POWER, null);
+ mForceLevel = State.LEVEL_LOW;
int seq = mSeq.incrementAndGet();
- mHandler.sendMessage(mHandler.obtainMessage(FORCE_MEMORY_WHAT,
- opts | OPTION_UPDATES_STOPPED | OPTION_STORAGE_LOW, seq));
if ((opts & OPTION_FORCE_UPDATE) != 0) {
+ mHandler.removeMessages(MSG_CHECK);
+ mHandler.obtainMessage(MSG_CHECK).sendToTarget();
pw.println(seq);
}
} break;
@@ -529,10 +378,11 @@ public class DeviceStorageMonitorService extends SystemService {
int opts = parseOptions(shell);
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.DEVICE_POWER, null);
+ mForceLevel = State.LEVEL_NORMAL;
int seq = mSeq.incrementAndGet();
- mHandler.sendMessage(mHandler.obtainMessage(FORCE_MEMORY_WHAT,
- opts | OPTION_UPDATES_STOPPED, seq));
if ((opts & OPTION_FORCE_UPDATE) != 0) {
+ mHandler.removeMessages(MSG_CHECK);
+ mHandler.obtainMessage(MSG_CHECK).sendToTarget();
pw.println(seq);
}
} break;
@@ -540,10 +390,11 @@ public class DeviceStorageMonitorService extends SystemService {
int opts = parseOptions(shell);
getContext().enforceCallingOrSelfPermission(
android.Manifest.permission.DEVICE_POWER, null);
+ mForceLevel = State.LEVEL_UNKNOWN;
int seq = mSeq.incrementAndGet();
- mHandler.sendMessage(mHandler.obtainMessage(FORCE_MEMORY_WHAT,
- opts, seq));
if ((opts & OPTION_FORCE_UPDATE) != 0) {
+ mHandler.removeMessages(MSG_CHECK);
+ mHandler.obtainMessage(MSG_CHECK).sendToTarget();
pw.println(seq);
}
} break;
@@ -568,145 +419,125 @@ public class DeviceStorageMonitorService extends SystemService {
pw.println(" -f: force a storage change broadcast be sent, prints new sequence.");
}
- void dumpImpl(FileDescriptor fd, PrintWriter pw, String[] args) {
+ void dumpImpl(FileDescriptor fd, PrintWriter _pw, String[] args) {
+ final IndentingPrintWriter pw = new IndentingPrintWriter(_pw, " ");
if (args == null || args.length == 0 || "-a".equals(args[0])) {
- final Context context = getContext();
-
- pw.println("Current DeviceStorageMonitor state:");
-
- pw.print(" mFreeMem=");
- pw.print(Formatter.formatFileSize(context, mFreeMem));
- pw.print(" mTotalMemory=");
- pw.println(Formatter.formatFileSize(context, mTotalMemory));
-
- pw.print(" mFreeMemAfterLastCacheClear=");
- pw.println(Formatter.formatFileSize(context, mFreeMemAfterLastCacheClear));
-
- pw.print(" mLastReportedFreeMem=");
- pw.print(Formatter.formatFileSize(context, mLastReportedFreeMem));
- pw.print(" mLastReportedFreeMemTime=");
- TimeUtils.formatDuration(mLastReportedFreeMemTime, SystemClock.elapsedRealtime(), pw);
+ pw.println("Known volumes:");
+ pw.increaseIndent();
+ for (int i = 0; i < mStates.size(); i++) {
+ final UUID uuid = mStates.keyAt(i);
+ final State state = mStates.valueAt(i);
+ if (StorageManager.UUID_DEFAULT.equals(uuid)) {
+ pw.println("Default:");
+ } else {
+ pw.println(uuid + ":");
+ }
+ pw.increaseIndent();
+ pw.printPair("level", State.levelToString(state.level));
+ pw.printPair("lastUsableBytes", state.lastUsableBytes);
+ pw.println();
+ pw.decreaseIndent();
+ }
+ pw.decreaseIndent();
pw.println();
- if (mUpdatesStopped) {
- pw.print(" mUpdatesStopped=");
- pw.print(mUpdatesStopped);
- pw.print(" mSeq=");
- pw.println(mSeq.get());
- } else {
- pw.print(" mClearSucceeded=");
- pw.print(mClearSucceeded);
- pw.print(" mClearingCache=");
- pw.println(mClearingCache);
- }
+ pw.printPair("mSeq", mSeq.get());
+ pw.printPair("mForceState", State.levelToString(mForceLevel));
+ pw.println();
+ pw.println();
- pw.print(" mLowMemFlag=");
- pw.print(mLowMemFlag);
- pw.print(" mMemFullFlag=");
- pw.println(mMemFullFlag);
-
- pw.print(" mMemLowThreshold=");
- pw.print(Formatter.formatFileSize(context, mMemLowThreshold));
- pw.print(" mMemFullThreshold=");
- pw.println(Formatter.formatFileSize(context, mMemFullThreshold));
-
- pw.print(" mMemCacheStartTrimThreshold=");
- pw.print(Formatter.formatFileSize(context, mMemCacheStartTrimThreshold));
- pw.print(" mMemCacheTrimToThreshold=");
- pw.println(Formatter.formatFileSize(context, mMemCacheTrimToThreshold));
-
- pw.print(" mIsBootImageOnDisk="); pw.println(mIsBootImageOnDisk);
} else {
Shell shell = new Shell();
shell.exec(mRemoteService, null, fd, null, args, null, new ResultReceiver(null));
}
}
- /**
- * This method sends a notification to NotificationManager to display
- * an error dialog indicating low disk space and launch the Installer
- * application
- */
- private void sendNotification(int seq) {
+ private void updateNotifications(VolumeInfo vol, int oldLevel, int newLevel) {
final Context context = getContext();
- if(localLOGV) Slog.i(TAG, "Sending low memory notification");
- //log the event to event log with the amount of free storage(in bytes) left on the device
- EventLog.writeEvent(EventLogTags.LOW_STORAGE, mFreeMem);
- // Pack up the values and broadcast them to everyone
- Intent lowMemIntent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
- lowMemIntent.putExtra("memory", mFreeMem);
- lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- NotificationManager notificationMgr =
- (NotificationManager)context.getSystemService(
- Context.NOTIFICATION_SERVICE);
- CharSequence title = context.getText(
- com.android.internal.R.string.low_internal_storage_view_title);
- CharSequence details = context.getText(mIsBootImageOnDisk
- ? com.android.internal.R.string.low_internal_storage_view_text
- : com.android.internal.R.string.low_internal_storage_view_text_no_boot);
- PendingIntent intent = PendingIntent.getActivityAsUser(context, 0, lowMemIntent, 0,
- null, UserHandle.CURRENT);
- Notification notification =
- new Notification.Builder(context, SystemNotificationChannels.ALERTS)
- .setSmallIcon(com.android.internal.R.drawable.stat_notify_disk_full)
- .setTicker(title)
- .setColor(context.getColor(
- com.android.internal.R.color.system_notification_accent_color))
- .setContentTitle(title)
- .setContentText(details)
- .setContentIntent(intent)
- .setStyle(new Notification.BigTextStyle()
- .bigText(details))
- .setVisibility(Notification.VISIBILITY_PUBLIC)
- .setCategory(Notification.CATEGORY_SYSTEM)
- .extend(new Notification.TvExtender()
- .setChannelId(TV_NOTIFICATION_CHANNEL_ID))
- .build();
- notification.flags |= Notification.FLAG_NO_CLEAR;
- notificationMgr.notifyAsUser(null, SystemMessage.NOTE_LOW_STORAGE, notification,
- UserHandle.ALL);
- Intent broadcast = new Intent(mStorageLowIntent);
- if (seq != 0) {
- broadcast.putExtra(EXTRA_SEQUENCE, seq);
+ final UUID uuid = StorageManager.convert(vol.getFsUuid());
+
+ if (State.isEntering(State.LEVEL_LOW, oldLevel, newLevel)) {
+ Intent lowMemIntent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
+ lowMemIntent.putExtra(StorageManager.EXTRA_UUID, uuid);
+ lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+ final CharSequence title = context.getText(
+ com.android.internal.R.string.low_internal_storage_view_title);
+
+ final CharSequence details;
+ if (StorageManager.UUID_DEFAULT.equals(uuid)) {
+ details = context.getText(isBootImageOnDisk()
+ ? com.android.internal.R.string.low_internal_storage_view_text
+ : com.android.internal.R.string.low_internal_storage_view_text_no_boot);
+ } else {
+ details = context.getText(
+ com.android.internal.R.string.low_internal_storage_view_text);
+ }
+
+ PendingIntent intent = PendingIntent.getActivityAsUser(context, 0, lowMemIntent, 0,
+ null, UserHandle.CURRENT);
+ Notification notification =
+ new Notification.Builder(context, SystemNotificationChannels.ALERTS)
+ .setSmallIcon(com.android.internal.R.drawable.stat_notify_disk_full)
+ .setTicker(title)
+ .setColor(context.getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setContentTitle(title)
+ .setContentText(details)
+ .setContentIntent(intent)
+ .setStyle(new Notification.BigTextStyle()
+ .bigText(details))
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .setCategory(Notification.CATEGORY_SYSTEM)
+ .extend(new Notification.TvExtender()
+ .setChannelId(TV_NOTIFICATION_CHANNEL_ID))
+ .build();
+ notification.flags |= Notification.FLAG_NO_CLEAR;
+ mNotifManager.notifyAsUser(uuid.toString(), SystemMessage.NOTE_LOW_STORAGE,
+ notification, UserHandle.ALL);
+ } else if (State.isLeaving(State.LEVEL_LOW, oldLevel, newLevel)) {
+ mNotifManager.cancelAsUser(uuid.toString(), SystemMessage.NOTE_LOW_STORAGE,
+ UserHandle.ALL);
}
- context.sendStickyBroadcastAsUser(broadcast, UserHandle.ALL);
}
- /**
- * Cancels low storage notification and sends OK intent.
- */
- private void cancelNotification(int seq) {
- final Context context = getContext();
- if(localLOGV) Slog.i(TAG, "Canceling low memory notification");
- NotificationManager mNotificationMgr =
- (NotificationManager)context.getSystemService(
- Context.NOTIFICATION_SERVICE);
- //cancel notification since memory has been freed
- mNotificationMgr.cancelAsUser(null, SystemMessage.NOTE_LOW_STORAGE, UserHandle.ALL);
-
- context.removeStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
- Intent broadcast = new Intent(mStorageOkIntent);
- if (seq != 0) {
- broadcast.putExtra(EXTRA_SEQUENCE, seq);
+ private void updateBroadcasts(VolumeInfo vol, int oldLevel, int newLevel, int seq) {
+ if (!Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, vol.getFsUuid())) {
+ // We don't currently send broadcasts for secondary volumes
+ return;
}
- context.sendBroadcastAsUser(broadcast, UserHandle.ALL);
- }
- /**
- * Send a notification when storage is full.
- */
- private void sendFullNotification() {
- if(localLOGV) Slog.i(TAG, "Sending memory full notification");
- getContext().sendStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL);
- }
+ final Intent lowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS)
+ .putExtra(EXTRA_SEQUENCE, seq);
+ final Intent notLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+ | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
+ | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS)
+ .putExtra(EXTRA_SEQUENCE, seq);
- /**
- * Cancels memory full notification and sends "not full" intent.
- */
- private void cancelFullNotification() {
- if(localLOGV) Slog.i(TAG, "Canceling memory full notification");
- getContext().removeStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL);
- getContext().sendBroadcastAsUser(mStorageNotFullIntent, UserHandle.ALL);
+ if (State.isEntering(State.LEVEL_LOW, oldLevel, newLevel)) {
+ getContext().sendStickyBroadcastAsUser(lowIntent, UserHandle.ALL);
+ } else if (State.isLeaving(State.LEVEL_LOW, oldLevel, newLevel)) {
+ getContext().removeStickyBroadcastAsUser(lowIntent, UserHandle.ALL);
+ getContext().sendBroadcastAsUser(notLowIntent, UserHandle.ALL);
+ }
+
+ final Intent fullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)
+ .putExtra(EXTRA_SEQUENCE, seq);
+ final Intent notFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)
+ .putExtra(EXTRA_SEQUENCE, seq);
+
+ if (State.isEntering(State.LEVEL_FULL, oldLevel, newLevel)) {
+ getContext().sendStickyBroadcastAsUser(fullIntent, UserHandle.ALL);
+ } else if (State.isLeaving(State.LEVEL_FULL, oldLevel, newLevel)) {
+ getContext().removeStickyBroadcastAsUser(fullIntent, UserHandle.ALL);
+ getContext().sendBroadcastAsUser(notFullIntent, UserHandle.ALL);
+ }
}
private static class CacheFileDeletedObserver extends FileObserver {
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 16b73d5515e5d..562443f53546c 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -62,6 +62,8 @@ import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.storage.CacheQuotaStrategy;
+import java.io.File;
+import java.io.FileNotFoundException;
import java.io.IOException;
public class StorageStatsService extends IStorageStatsManager.Stub {
@@ -181,29 +183,42 @@ public class StorageStatsService extends IStorageStatsManager.Stub {
public long getFreeBytes(String volumeUuid, String callingPackage) {
// NOTE: No permissions required
- long cacheBytes = 0;
final long token = Binder.clearCallingIdentity();
try {
+ final File path;
+ try {
+ path = mStorage.findPathForUuid(volumeUuid);
+ } catch (FileNotFoundException e) {
+ throw new ParcelableException(e);
+ }
+
+ // Free space is usable bytes plus any cached data that we're
+ // willing to automatically clear. To avoid user confusion, this
+ // logic should be kept in sync with getAllocatableBytes().
if (isQuotaSupported(volumeUuid, callingPackage)) {
- for (UserInfo user : mUser.getUsers()) {
- final StorageStats stats = queryStatsForUser(volumeUuid, user.id, null);
- cacheBytes += stats.cacheBytes;
- }
+ final long cacheTotal = getCacheBytes(volumeUuid, callingPackage);
+ final long cacheReserved = mStorage.getStorageCacheBytes(path);
+ final long cacheClearable = Math.max(0, cacheTotal - cacheReserved);
+
+ return path.getUsableSpace() + cacheClearable;
+ } else {
+ return path.getUsableSpace();
}
} finally {
Binder.restoreCallingIdentity(token);
}
+ }
- if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
- return Environment.getDataDirectory().getFreeSpace() + cacheBytes;
- } else {
- final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid);
- if (vol == null) {
- throw new ParcelableException(
- new IOException("Failed to find storage device for UUID " + volumeUuid));
- }
- return vol.getPath().getFreeSpace() + cacheBytes;
+ @Override
+ public long getCacheBytes(String volumeUuid, String callingPackage) {
+ enforcePermission(Binder.getCallingUid(), callingPackage);
+
+ long cacheBytes = 0;
+ for (UserInfo user : mUser.getUsers()) {
+ final StorageStats stats = queryStatsForUser(volumeUuid, user.id, null);
+ cacheBytes += stats.cacheBytes;
}
+ return cacheBytes;
}
@Override