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