DeviceStorageMonitorServicepublic class DeviceStorageMonitorService extends com.android.server.SystemService 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. |
Fields Summary |
---|
static final String | TAG | static final boolean | DEBUG | static final boolean | localLOGV | static final int | DEVICE_MEMORY_WHAT | private static final int | MONITOR_INTERVAL | private static final int | LOW_MEMORY_NOTIFICATION_ID | private static final int | DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES | private static final long | DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD | private static final long | DEFAULT_CHECK_INTERVAL | private long | mFreeMem | private long | mFreeMemAfterLastCacheClear | private long | mLastReportedFreeMem | private long | mLastReportedFreeMemTime | boolean | mLowMemFlag | private boolean | mMemFullFlag | private final boolean | mIsBootImageOnDisk | private final android.content.ContentResolver | mResolver | private final long | mTotalMemory | private final android.os.StatFs | mDataFileStats | private final android.os.StatFs | mSystemFileStats | private final android.os.StatFs | mCacheFileStats | private static final File | DATA_PATH | private static final File | SYSTEM_PATH | private static final File | CACHE_PATH | private long | mThreadStartTime | boolean | mClearSucceeded | boolean | mClearingCache | private final android.content.Intent | mStorageLowIntent | private final android.content.Intent | mStorageOkIntent | private final android.content.Intent | mStorageFullIntent | private final android.content.Intent | mStorageNotFullIntent | private CachePackageDataObserver | mClearCacheObserver | private CacheFileDeletedObserver | mCacheFileDeletedObserver | private static final int | _TRUE | private static final int | _FALSE | long | mMemLowThreshold | private long | mMemCacheStartTrimThreshold | private long | mMemCacheTrimToThreshold | private long | mMemFullThreshold | static final String | SERVICEThis string is used for ServiceManager access to this class. | private final android.os.Handler | mHandlerHandler that checks the amount of disk space on the device and sends a
notification if the device runs low on disk space | private final DeviceStorageMonitorInternal | mLocalService | private final android.os.IBinder | mRemoteService |
Constructors Summary |
---|
public DeviceStorageMonitorService(android.content.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);
mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK);
mStorageOkIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
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);
|
Methods Summary |
---|
private void | cancelFullNotification()Cancels memory full notification and sends "not full" intent.
if(localLOGV) Slog.i(TAG, "Canceling memory full notification");
getContext().removeStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL);
getContext().sendBroadcastAsUser(mStorageNotFullIntent, UserHandle.ALL);
| private void | cancelNotification()Cancels low storage notification and sends OK intent.
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, LOW_MEMORY_NOTIFICATION_ID, UserHandle.ALL);
context.removeStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
context.sendBroadcastAsUser(mStorageOkIntent, UserHandle.ALL);
| void | checkMemory(boolean checkCache)
//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();
mLowMemFlag = true;
} else {
if (localLOGV) Slog.v(TAG, "Running low on memory " +
"notification already sent. do nothing");
}
}
} else {
mFreeMemAfterLastCacheClear = mFreeMem;
if (mLowMemFlag) {
Slog.i(TAG, "Memory available. Cancelling notification");
cancelNotification();
mLowMemFlag = false;
}
}
if (!mLowMemFlag && !mIsBootImageOnDisk) {
Slog.i(TAG, "No boot image on disk due to lack of space. Sending notification");
sendNotification();
}
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);
| 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(mMemCacheTrimToThreshold, mClearCacheObserver);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
mClearingCache = false;
mClearSucceeded = false;
}
| void | dumpImpl(java.io.PrintWriter pw)
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();
pw.print(" mLowMemFlag="); pw.print(mLowMemFlag);
pw.print(" mMemFullFlag="); pw.println(mMemFullFlag);
pw.print(" mIsBootImageOnDisk="); pw.print(mIsBootImageOnDisk);
pw.print(" mClearSucceeded="); pw.print(mClearSucceeded);
pw.print(" mClearingCache="); pw.println(mClearingCache);
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));
| private static boolean | isBootImageOnDisk()
for (String instructionSet : PackageManagerService.getAllDexCodeInstructionSets()) {
if (!VMRuntime.isBootClassPathOnDisk(instructionSet)) {
return false;
}
}
return true;
| public void | onStart()Initializes the disk space threshold value and posts an empty message to
kickstart the process.
// cache storage thresholds
final StorageManager sm = StorageManager.from(getContext());
mMemLowThreshold = sm.getStorageLowBytes(DATA_PATH);
mMemFullThreshold = sm.getStorageFullBytes(DATA_PATH);
mMemCacheStartTrimThreshold = ((mMemLowThreshold*3)+mMemFullThreshold)/4;
mMemCacheTrimToThreshold = mMemLowThreshold
+ ((mMemLowThreshold-mMemCacheStartTrimThreshold)*2);
mFreeMemAfterLastCacheClear = mTotalMemory;
checkMemory(true);
mCacheFileDeletedObserver = new CacheFileDeletedObserver();
mCacheFileDeletedObserver.startWatching();
publishBinderService(SERVICE, mRemoteService);
publishLocalService(DeviceStorageMonitorInternal.class, mLocalService);
| 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);
| 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);
}
| private void | sendFullNotification()Send a notification when storage is full.
if(localLOGV) Slog.i(TAG, "Sending memory full notification");
getContext().sendStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL);
| private void | sendNotification()This method sends a notification to NotificationManager to display
an error dialog indicating low disk space and launch the Installer
application
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(Environment.isExternalStorageEmulated()
? Settings.ACTION_INTERNAL_STORAGE_SETTINGS
: Intent.ACTION_MANAGE_PACKAGE_STORAGE);
lowMemIntent.putExtra("memory", mFreeMem);
lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
NotificationManager mNotificationMgr =
(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)
.setSmallIcon(com.android.internal.R.drawable.stat_notify_disk_full)
.setTicker(title)
.setColor(context.getResources().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)
.build();
notification.flags |= Notification.FLAG_NO_CLEAR;
mNotificationMgr.notifyAsUser(null, LOW_MEMORY_NOTIFICATION_ID, notification,
UserHandle.ALL);
context.sendStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
|
|