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