FileDocCategorySizeDatePackage
DeviceStorageMonitorService.javaAPI DocAndroid 5.1 API23522Thu Mar 12 22:22:42 GMT 2015com.android.server.storage

DeviceStorageMonitorService

public 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
SERVICE
This string is used for ServiceManager access to this class.
private final android.os.Handler
mHandler
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 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 voidcancelFullNotification()
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 voidcancelNotification()
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);
    
voidcheckMemory(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 voidclearCache()

        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;
        }
    
voiddumpImpl(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 booleanisBootImageOnDisk()

        for (String instructionSet : PackageManagerService.getAllDexCodeInstructionSets()) {
            if (!VMRuntime.isBootClassPathOnDisk(instructionSet)) {
                return false;
            }
        }
        return true;
    
public voidonStart()
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);
    
voidpostCheckMemoryMsg(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 voidrestatDataDir()

        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 voidsendFullNotification()
Send a notification when storage is full.

        if(localLOGV) Slog.i(TAG, "Sending memory full notification");
        getContext().sendStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL);
    
private voidsendNotification()
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);