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