DeviceStorageMonitorService.java revision 197a0c82a1fbf337ec0a85d36b6b89c3d6e8a0ac
1/*
2 * Copyright (C) 2007-2008 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
17package com.android.server;
18
19import java.io.FileDescriptor;
20import java.io.PrintWriter;
21
22import android.app.Notification;
23import android.app.NotificationManager;
24import android.app.PendingIntent;
25import android.content.ContentResolver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.pm.IPackageDataObserver;
29import android.content.pm.IPackageManager;
30import android.content.pm.PackageManager;
31import android.os.Binder;
32import android.os.Environment;
33import android.os.FileObserver;
34import android.os.Handler;
35import android.os.Message;
36import android.os.Process;
37import android.os.RemoteException;
38import android.os.ServiceManager;
39import android.os.StatFs;
40import android.os.SystemClock;
41import android.os.SystemProperties;
42import android.provider.Settings;
43import android.text.format.Formatter;
44import android.util.EventLog;
45import android.util.Slog;
46import android.util.TimeUtils;
47
48/**
49 * This class implements a service to monitor the amount of disk
50 * storage space on the device.  If the free storage on device is less
51 * than a tunable threshold value (a secure settings parameter;
52 * default 10%) a low memory notification is displayed to alert the
53 * user. If the user clicks on the low memory notification the
54 * Application Manager application gets launched to let the user free
55 * storage space.
56 *
57 * Event log events: A low memory event with the free storage on
58 * device in bytes is logged to the event log when the device goes low
59 * on storage space.  The amount of free storage on the device is
60 * periodically logged to the event log. The log interval is a secure
61 * settings parameter with a default value of 12 hours.  When the free
62 * storage differential goes below a threshold (again a secure
63 * settings parameter with a default value of 2MB), the free memory is
64 * logged to the event log.
65 */
66public class DeviceStorageMonitorService extends Binder {
67    private static final String TAG = "DeviceStorageMonitorService";
68    private static final boolean DEBUG = false;
69    private static final boolean localLOGV = false;
70    private static final int DEVICE_MEMORY_WHAT = 1;
71    private static final int MONITOR_INTERVAL = 1; //in minutes
72    private static final int LOW_MEMORY_NOTIFICATION_ID = 1;
73    private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
74    private static final int DEFAULT_THRESHOLD_MAX_BYTES = 500*1024*1024; // 500MB
75    private static final int DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES = 12*60; //in minutes
76    private static final long DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD = 2 * 1024 * 1024; // 2MB
77    private static final long DEFAULT_CHECK_INTERVAL = MONITOR_INTERVAL*60*1000;
78    private static final int DEFAULT_FULL_THRESHOLD_BYTES = 1024*1024; // 1MB
79    private long mFreeMem;  // on /data
80    private long mFreeMemAfterLastCacheClear;  // on /data
81    private long mLastReportedFreeMem;
82    private long mLastReportedFreeMemTime;
83    private boolean mLowMemFlag=false;
84    private boolean mMemFullFlag=false;
85    private Context mContext;
86    private ContentResolver mContentResolver;
87    private long mTotalMemory;  // on /data
88    private StatFs mDataFileStats;
89    private StatFs mSystemFileStats;
90    private StatFs mCacheFileStats;
91    private static final String DATA_PATH = "/data";
92    private static final String SYSTEM_PATH = "/system";
93    private static final String CACHE_PATH = "/cache";
94    private long mThreadStartTime = -1;
95    private boolean mClearSucceeded = false;
96    private boolean mClearingCache;
97    private Intent mStorageLowIntent;
98    private Intent mStorageOkIntent;
99    private Intent mStorageFullIntent;
100    private Intent mStorageNotFullIntent;
101    private CachePackageDataObserver mClearCacheObserver;
102    private final CacheFileDeletedObserver mCacheFileDeletedObserver;
103    private static final int _TRUE = 1;
104    private static final int _FALSE = 0;
105    // This is the raw threshold that has been set at which we consider
106    // storage to be low.
107    private long mMemLowThreshold;
108    // This is the threshold at which we start trying to flush caches
109    // to get below the low threshold limit.  It is less than the low
110    // threshold; we will allow storage to get a bit beyond the limit
111    // before flushing and checking if we are actually low.
112    private long mMemCacheStartTrimThreshold;
113    // This is the threshold that we try to get to when deleting cache
114    // files.  This is greater than the low threshold so that we will flush
115    // more files than absolutely needed, to reduce the frequency that
116    // flushing takes place.
117    private long mMemCacheTrimToThreshold;
118    private int mMemFullThreshold;
119
120    /**
121     * This string is used for ServiceManager access to this class.
122     */
123    public static final String SERVICE = "devicestoragemonitor";
124
125    /**
126    * Handler that checks the amount of disk space on the device and sends a
127    * notification if the device runs low on disk space
128    */
129    Handler mHandler = new Handler() {
130        @Override
131        public void handleMessage(Message msg) {
132            //don't handle an invalid message
133            if (msg.what != DEVICE_MEMORY_WHAT) {
134                Slog.e(TAG, "Will not process invalid message");
135                return;
136            }
137            checkMemory(msg.arg1 == _TRUE);
138        }
139    };
140
141    class CachePackageDataObserver extends IPackageDataObserver.Stub {
142        public void onRemoveCompleted(String packageName, boolean succeeded) {
143            mClearSucceeded = succeeded;
144            mClearingCache = false;
145            if(localLOGV) Slog.i(TAG, " Clear succeeded:"+mClearSucceeded
146                    +", mClearingCache:"+mClearingCache+" Forcing memory check");
147            postCheckMemoryMsg(false, 0);
148        }
149    }
150
151    private final void restatDataDir() {
152        try {
153            mDataFileStats.restat(DATA_PATH);
154            mFreeMem = (long) mDataFileStats.getAvailableBlocks() *
155                mDataFileStats.getBlockSize();
156        } catch (IllegalArgumentException e) {
157            // use the old value of mFreeMem
158        }
159        // Allow freemem to be overridden by debug.freemem for testing
160        String debugFreeMem = SystemProperties.get("debug.freemem");
161        if (!"".equals(debugFreeMem)) {
162            mFreeMem = Long.parseLong(debugFreeMem);
163        }
164        // Read the log interval from secure settings
165        long freeMemLogInterval = Settings.Secure.getLong(mContentResolver,
166                Settings.Secure.SYS_FREE_STORAGE_LOG_INTERVAL,
167                DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES)*60*1000;
168        //log the amount of free memory in event log
169        long currTime = SystemClock.elapsedRealtime();
170        if((mLastReportedFreeMemTime == 0) ||
171           (currTime-mLastReportedFreeMemTime) >= freeMemLogInterval) {
172            mLastReportedFreeMemTime = currTime;
173            long mFreeSystem = -1, mFreeCache = -1;
174            try {
175                mSystemFileStats.restat(SYSTEM_PATH);
176                mFreeSystem = (long) mSystemFileStats.getAvailableBlocks() *
177                    mSystemFileStats.getBlockSize();
178            } catch (IllegalArgumentException e) {
179                // ignore; report -1
180            }
181            try {
182                mCacheFileStats.restat(CACHE_PATH);
183                mFreeCache = (long) mCacheFileStats.getAvailableBlocks() *
184                    mCacheFileStats.getBlockSize();
185            } catch (IllegalArgumentException e) {
186                // ignore; report -1
187            }
188            EventLog.writeEvent(EventLogTags.FREE_STORAGE_LEFT,
189                                mFreeMem, mFreeSystem, mFreeCache);
190        }
191        // Read the reporting threshold from secure settings
192        long threshold = Settings.Secure.getLong(mContentResolver,
193                Settings.Secure.DISK_FREE_CHANGE_REPORTING_THRESHOLD,
194                DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD);
195        // If mFree changed significantly log the new value
196        long delta = mFreeMem - mLastReportedFreeMem;
197        if (delta > threshold || delta < -threshold) {
198            mLastReportedFreeMem = mFreeMem;
199            EventLog.writeEvent(EventLogTags.FREE_STORAGE_CHANGED, mFreeMem);
200        }
201    }
202
203    private final void clearCache() {
204        if (mClearCacheObserver == null) {
205            // Lazy instantiation
206            mClearCacheObserver = new CachePackageDataObserver();
207        }
208        mClearingCache = true;
209        try {
210            if (localLOGV) Slog.i(TAG, "Clearing cache");
211            IPackageManager.Stub.asInterface(ServiceManager.getService("package")).
212                    freeStorageAndNotify(mMemCacheTrimToThreshold, mClearCacheObserver);
213        } catch (RemoteException e) {
214            Slog.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
215            mClearingCache = false;
216            mClearSucceeded = false;
217        }
218    }
219
220    private final void checkMemory(boolean checkCache) {
221        //if the thread that was started to clear cache is still running do nothing till its
222        //finished clearing cache. Ideally this flag could be modified by clearCache
223        // and should be accessed via a lock but even if it does this test will fail now and
224        //hopefully the next time this flag will be set to the correct value.
225        if(mClearingCache) {
226            if(localLOGV) Slog.i(TAG, "Thread already running just skip");
227            //make sure the thread is not hung for too long
228            long diffTime = System.currentTimeMillis() - mThreadStartTime;
229            if(diffTime > (10*60*1000)) {
230                Slog.w(TAG, "Thread that clears cache file seems to run for ever");
231            }
232        } else {
233            restatDataDir();
234            if (localLOGV)  Slog.v(TAG, "freeMemory="+mFreeMem);
235
236            //post intent to NotificationManager to display icon if necessary
237            if (mFreeMem < mMemLowThreshold) {
238                if (checkCache) {
239                    // We are allowed to clear cache files at this point to
240                    // try to get down below the limit, because this is not
241                    // the initial call after a cache clear has been attempted.
242                    // In this case we will try a cache clear if our free
243                    // space has gone below the cache clear limit.
244                    if (mFreeMem < mMemCacheStartTrimThreshold) {
245                        // We only clear the cache if the free storage has changed
246                        // a significant amount since the last time.
247                        if ((mFreeMemAfterLastCacheClear-mFreeMem)
248                                >= ((mMemLowThreshold-mMemCacheStartTrimThreshold)/4)) {
249                            // See if clearing cache helps
250                            // Note that clearing cache is asynchronous and so we do a
251                            // memory check again once the cache has been cleared.
252                            mThreadStartTime = System.currentTimeMillis();
253                            mClearSucceeded = false;
254                            clearCache();
255                        }
256                    }
257                } else {
258                    // This is a call from after clearing the cache.  Note
259                    // the amount of free storage at this point.
260                    mFreeMemAfterLastCacheClear = mFreeMem;
261                    if (!mLowMemFlag) {
262                        // We tried to clear the cache, but that didn't get us
263                        // below the low storage limit.  Tell the user.
264                        Slog.i(TAG, "Running low on memory. Sending notification");
265                        sendNotification();
266                        mLowMemFlag = true;
267                    } else {
268                        if (localLOGV) Slog.v(TAG, "Running low on memory " +
269                                "notification already sent. do nothing");
270                    }
271                }
272            } else {
273                mFreeMemAfterLastCacheClear = mFreeMem;
274                if (mLowMemFlag) {
275                    Slog.i(TAG, "Memory available. Cancelling notification");
276                    cancelNotification();
277                    mLowMemFlag = false;
278                }
279            }
280            if (mFreeMem < mMemFullThreshold) {
281                if (!mMemFullFlag) {
282                    sendFullNotification();
283                    mMemFullFlag = true;
284                }
285            } else {
286                if (mMemFullFlag) {
287                    cancelFullNotification();
288                    mMemFullFlag = false;
289                }
290            }
291        }
292        if(localLOGV) Slog.i(TAG, "Posting Message again");
293        //keep posting messages to itself periodically
294        postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL);
295    }
296
297    private void postCheckMemoryMsg(boolean clearCache, long delay) {
298        // Remove queued messages
299        mHandler.removeMessages(DEVICE_MEMORY_WHAT);
300        mHandler.sendMessageDelayed(mHandler.obtainMessage(DEVICE_MEMORY_WHAT,
301                clearCache ?_TRUE : _FALSE, 0),
302                delay);
303    }
304
305    /*
306     * just query settings to retrieve the memory threshold.
307     * Preferred this over using a ContentObserver since Settings.Secure caches the value
308     * any way
309     */
310    private long getMemThreshold() {
311        long value = Settings.Secure.getInt(
312                              mContentResolver,
313                              Settings.Secure.SYS_STORAGE_THRESHOLD_PERCENTAGE,
314                              DEFAULT_THRESHOLD_PERCENTAGE);
315        if(localLOGV) Slog.v(TAG, "Threshold Percentage="+value);
316        value = (value*mTotalMemory)/100;
317        long maxValue = Settings.Secure.getInt(
318                mContentResolver,
319                Settings.Secure.SYS_STORAGE_THRESHOLD_MAX_BYTES,
320                DEFAULT_THRESHOLD_MAX_BYTES);
321        //evaluate threshold value
322        return value < maxValue ? value : maxValue;
323    }
324
325    /*
326     * just query settings to retrieve the memory full threshold.
327     * Preferred this over using a ContentObserver since Settings.Secure caches the value
328     * any way
329     */
330    private int getMemFullThreshold() {
331        int value = Settings.Secure.getInt(
332                              mContentResolver,
333                              Settings.Secure.SYS_STORAGE_FULL_THRESHOLD_BYTES,
334                              DEFAULT_FULL_THRESHOLD_BYTES);
335        if(localLOGV) Slog.v(TAG, "Full Threshold Bytes="+value);
336        return value;
337    }
338
339    /**
340    * Constructor to run service. initializes the disk space threshold value
341    * and posts an empty message to kickstart the process.
342    */
343    public DeviceStorageMonitorService(Context context) {
344        mLastReportedFreeMemTime = 0;
345        mContext = context;
346        mContentResolver = mContext.getContentResolver();
347        //create StatFs object
348        mDataFileStats = new StatFs(DATA_PATH);
349        mSystemFileStats = new StatFs(SYSTEM_PATH);
350        mCacheFileStats = new StatFs(CACHE_PATH);
351        //initialize total storage on device
352        mTotalMemory = (long)mDataFileStats.getBlockCount() *
353                        mDataFileStats.getBlockSize();
354        mStorageLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW);
355        mStorageLowIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
356        mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK);
357        mStorageOkIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
358        mStorageFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL);
359        mStorageFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
360        mStorageNotFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
361        mStorageNotFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
362        // cache storage thresholds
363        mMemLowThreshold = getMemThreshold();
364        mMemFullThreshold = getMemFullThreshold();
365        mMemCacheStartTrimThreshold = ((mMemLowThreshold*3)+mMemFullThreshold)/4;
366        mMemCacheTrimToThreshold = mMemLowThreshold
367                + ((mMemLowThreshold-mMemCacheStartTrimThreshold)*2);
368        mFreeMemAfterLastCacheClear = mTotalMemory;
369        checkMemory(true);
370
371        mCacheFileDeletedObserver = new CacheFileDeletedObserver();
372        mCacheFileDeletedObserver.startWatching();
373    }
374
375
376    /**
377    * This method sends a notification to NotificationManager to display
378    * an error dialog indicating low disk space and launch the Installer
379    * application
380    */
381    private final void sendNotification() {
382        if(localLOGV) Slog.i(TAG, "Sending low memory notification");
383        //log the event to event log with the amount of free storage(in bytes) left on the device
384        EventLog.writeEvent(EventLogTags.LOW_STORAGE, mFreeMem);
385        //  Pack up the values and broadcast them to everyone
386        Intent lowMemIntent = new Intent(Environment.isExternalStorageEmulated()
387                ? Settings.ACTION_INTERNAL_STORAGE_SETTINGS
388                : Intent.ACTION_MANAGE_PACKAGE_STORAGE);
389        lowMemIntent.putExtra("memory", mFreeMem);
390        lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
391        NotificationManager mNotificationMgr =
392                (NotificationManager)mContext.getSystemService(
393                        Context.NOTIFICATION_SERVICE);
394        CharSequence title = mContext.getText(
395                com.android.internal.R.string.low_internal_storage_view_title);
396        CharSequence details = mContext.getText(
397                com.android.internal.R.string.low_internal_storage_view_text);
398        PendingIntent intent = PendingIntent.getActivity(mContext, 0,  lowMemIntent, 0);
399        Notification notification = new Notification();
400        notification.icon = com.android.internal.R.drawable.stat_notify_disk_full;
401        notification.tickerText = title;
402        notification.flags |= Notification.FLAG_NO_CLEAR;
403        notification.setLatestEventInfo(mContext, title, details, intent);
404        mNotificationMgr.notify(LOW_MEMORY_NOTIFICATION_ID, notification);
405        mContext.sendStickyBroadcast(mStorageLowIntent);
406    }
407
408    /**
409     * Cancels low storage notification and sends OK intent.
410     */
411    private final void cancelNotification() {
412        if(localLOGV) Slog.i(TAG, "Canceling low memory notification");
413        NotificationManager mNotificationMgr =
414                (NotificationManager)mContext.getSystemService(
415                        Context.NOTIFICATION_SERVICE);
416        //cancel notification since memory has been freed
417        mNotificationMgr.cancel(LOW_MEMORY_NOTIFICATION_ID);
418
419        mContext.removeStickyBroadcast(mStorageLowIntent);
420        mContext.sendBroadcast(mStorageOkIntent);
421    }
422
423    /**
424     * Send a notification when storage is full.
425     */
426    private final void sendFullNotification() {
427        if(localLOGV) Slog.i(TAG, "Sending memory full notification");
428        mContext.sendStickyBroadcast(mStorageFullIntent);
429    }
430
431    /**
432     * Cancels memory full notification and sends "not full" intent.
433     */
434    private final void cancelFullNotification() {
435        if(localLOGV) Slog.i(TAG, "Canceling memory full notification");
436        mContext.removeStickyBroadcast(mStorageFullIntent);
437        mContext.sendBroadcast(mStorageNotFullIntent);
438    }
439
440    public void updateMemory() {
441        int callingUid = getCallingUid();
442        if(callingUid != Process.SYSTEM_UID) {
443            return;
444        }
445        // force an early check
446        postCheckMemoryMsg(true, 0);
447    }
448
449    /**
450     * Callable from other things in the system service to obtain the low memory
451     * threshold.
452     *
453     * @return low memory threshold in bytes
454     */
455    public long getMemoryLowThreshold() {
456        return mMemLowThreshold;
457    }
458
459    /**
460     * Callable from other things in the system process to check whether memory
461     * is low.
462     *
463     * @return true is memory is low
464     */
465    public boolean isMemoryLow() {
466        return mLowMemFlag;
467    }
468
469    public static class CacheFileDeletedObserver extends FileObserver {
470        public CacheFileDeletedObserver() {
471            super(Environment.getDownloadCacheDirectory().getAbsolutePath(), FileObserver.DELETE);
472        }
473
474        @Override
475        public void onEvent(int event, String path) {
476            EventLogTags.writeCacheFileDeleted(path);
477        }
478    }
479
480    @Override
481    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
482        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
483                != PackageManager.PERMISSION_GRANTED) {
484
485            pw.println("Permission Denial: can't dump " + SERVICE + " from from pid="
486                    + Binder.getCallingPid()
487                    + ", uid=" + Binder.getCallingUid());
488            return;
489        }
490
491        pw.println("Current DeviceStorageMonitor state:");
492        pw.print("  mFreeMem="); pw.print(Formatter.formatFileSize(mContext, mFreeMem));
493                pw.print(" mTotalMemory=");
494                pw.println(Formatter.formatFileSize(mContext, mTotalMemory));
495        pw.print("  mFreeMemAfterLastCacheClear=");
496                pw.println(Formatter.formatFileSize(mContext, mFreeMemAfterLastCacheClear));
497        pw.print("  mLastReportedFreeMem=");
498                pw.print(Formatter.formatFileSize(mContext, mLastReportedFreeMem));
499                pw.print(" mLastReportedFreeMemTime=");
500                TimeUtils.formatDuration(mLastReportedFreeMemTime, SystemClock.elapsedRealtime(), pw);
501                pw.println();
502        pw.print("  mLowMemFlag="); pw.print(mLowMemFlag);
503                pw.print(" mMemFullFlag="); pw.println(mMemFullFlag);
504        pw.print("  mClearSucceeded="); pw.print(mClearSucceeded);
505                pw.print(" mClearingCache="); pw.println(mClearingCache);
506        pw.print("  mMemLowThreshold=");
507                pw.print(Formatter.formatFileSize(mContext, mMemLowThreshold));
508                pw.print(" mMemFullThreshold=");
509                pw.println(Formatter.formatFileSize(mContext, mMemFullThreshold));
510        pw.print("  mMemCacheStartTrimThreshold=");
511                pw.print(Formatter.formatFileSize(mContext, mMemCacheStartTrimThreshold));
512                pw.print(" mMemCacheTrimToThreshold=");
513                pw.println(Formatter.formatFileSize(mContext, mMemCacheTrimToThreshold));
514    }
515}
516