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.os.Binder;
28import android.os.Handler;
29import android.os.Message;
30import android.os.Process;
31import android.os.RemoteException;
32import android.os.ServiceManager;
33import android.os.StatFs;
34import android.os.SystemClock;
35import android.os.SystemProperties;
36import android.provider.Settings;
37import android.util.EventLog;
38import android.util.Slog;
39
40/**
41 * This class implements a service to monitor the amount of disk
42 * storage space on the device.  If the free storage on device is less
43 * than a tunable threshold value (a secure settings parameter;
44 * default 10%) a low memory notification is displayed to alert the
45 * user. If the user clicks on the low memory notification the
46 * Application Manager application gets launched to let the user free
47 * storage space.
48 *
49 * Event log events: A low memory event with the free storage on
50 * device in bytes is logged to the event log when the device goes low
51 * on storage space.  The amount of free storage on the device is
52 * periodically logged to the event log. The log interval is a secure
53 * settings parameter with a default value of 12 hours.  When the free
54 * storage differential goes below a threshold (again a secure
55 * settings parameter with a default value of 2MB), the free memory is
56 * logged to the event log.
57 */
58public class DeviceStorageMonitorService extends Binder {
59    private static final String TAG = "DeviceStorageMonitorService";
60    private static final boolean DEBUG = false;
61    private static final boolean localLOGV = false;
62    private static final int DEVICE_MEMORY_WHAT = 1;
63    private static final int MONITOR_INTERVAL = 1; //in minutes
64    private static final int LOW_MEMORY_NOTIFICATION_ID = 1;
65    private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
66    private static final int DEFAULT_THRESHOLD_MAX_BYTES = 500*1024*1024; // 500MB
67    private static final int DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES = 12*60; //in minutes
68    private static final long DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD = 2 * 1024 * 1024; // 2MB
69    private static final long DEFAULT_CHECK_INTERVAL = MONITOR_INTERVAL*60*1000;
70    private static final int DEFAULT_FULL_THRESHOLD_BYTES = 1024*1024; // 1MB
71    private long mFreeMem;  // on /data
72    private long mLastReportedFreeMem;
73    private long mLastReportedFreeMemTime;
74    private boolean mLowMemFlag=false;
75    private boolean mMemFullFlag=false;
76    private Context mContext;
77    private ContentResolver mContentResolver;
78    private long mTotalMemory;  // on /data
79    private StatFs mDataFileStats;
80    private StatFs mSystemFileStats;
81    private StatFs mCacheFileStats;
82    private static final String DATA_PATH = "/data";
83    private static final String SYSTEM_PATH = "/system";
84    private static final String CACHE_PATH = "/cache";
85    private long mThreadStartTime = -1;
86    private boolean mClearSucceeded = false;
87    private boolean mClearingCache;
88    private Intent mStorageLowIntent;
89    private Intent mStorageOkIntent;
90    private Intent mStorageFullIntent;
91    private Intent mStorageNotFullIntent;
92    private CachePackageDataObserver mClearCacheObserver;
93    private static final int _TRUE = 1;
94    private static final int _FALSE = 0;
95    private long mMemLowThreshold;
96    private int mMemFullThreshold;
97
98    /**
99     * This string is used for ServiceManager access to this class.
100     */
101    public static final String SERVICE = "devicestoragemonitor";
102
103    /**
104    * Handler that checks the amount of disk space on the device and sends a
105    * notification if the device runs low on disk space
106    */
107    Handler mHandler = new Handler() {
108        @Override
109        public void handleMessage(Message msg) {
110            //don't handle an invalid message
111            if (msg.what != DEVICE_MEMORY_WHAT) {
112                Slog.e(TAG, "Will not process invalid message");
113                return;
114            }
115            checkMemory(msg.arg1 == _TRUE);
116        }
117    };
118
119    class CachePackageDataObserver extends IPackageDataObserver.Stub {
120        public void onRemoveCompleted(String packageName, boolean succeeded) {
121            mClearSucceeded = succeeded;
122            mClearingCache = false;
123            if(localLOGV) Slog.i(TAG, " Clear succeeded:"+mClearSucceeded
124                    +", mClearingCache:"+mClearingCache+" Forcing memory check");
125            postCheckMemoryMsg(false, 0);
126        }
127    }
128
129    private final void restatDataDir() {
130        try {
131            mDataFileStats.restat(DATA_PATH);
132            mFreeMem = (long) mDataFileStats.getAvailableBlocks() *
133                mDataFileStats.getBlockSize();
134        } catch (IllegalArgumentException e) {
135            // use the old value of mFreeMem
136        }
137        // Allow freemem to be overridden by debug.freemem for testing
138        String debugFreeMem = SystemProperties.get("debug.freemem");
139        if (!"".equals(debugFreeMem)) {
140            mFreeMem = Long.parseLong(debugFreeMem);
141        }
142        // Read the log interval from secure settings
143        long freeMemLogInterval = Settings.Secure.getLong(mContentResolver,
144                Settings.Secure.SYS_FREE_STORAGE_LOG_INTERVAL,
145                DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES)*60*1000;
146        //log the amount of free memory in event log
147        long currTime = SystemClock.elapsedRealtime();
148        if((mLastReportedFreeMemTime == 0) ||
149           (currTime-mLastReportedFreeMemTime) >= freeMemLogInterval) {
150            mLastReportedFreeMemTime = currTime;
151            long mFreeSystem = -1, mFreeCache = -1;
152            try {
153                mSystemFileStats.restat(SYSTEM_PATH);
154                mFreeSystem = (long) mSystemFileStats.getAvailableBlocks() *
155                    mSystemFileStats.getBlockSize();
156            } catch (IllegalArgumentException e) {
157                // ignore; report -1
158            }
159            try {
160                mCacheFileStats.restat(CACHE_PATH);
161                mFreeCache = (long) mCacheFileStats.getAvailableBlocks() *
162                    mCacheFileStats.getBlockSize();
163            } catch (IllegalArgumentException e) {
164                // ignore; report -1
165            }
166            EventLog.writeEvent(EventLogTags.FREE_STORAGE_LEFT,
167                                mFreeMem, mFreeSystem, mFreeCache);
168        }
169        // Read the reporting threshold from secure settings
170        long threshold = Settings.Secure.getLong(mContentResolver,
171                Settings.Secure.DISK_FREE_CHANGE_REPORTING_THRESHOLD,
172                DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD);
173        // If mFree changed significantly log the new value
174        long delta = mFreeMem - mLastReportedFreeMem;
175        if (delta > threshold || delta < -threshold) {
176            mLastReportedFreeMem = mFreeMem;
177            EventLog.writeEvent(EventLogTags.FREE_STORAGE_CHANGED, mFreeMem);
178        }
179    }
180
181    private final void clearCache() {
182        if (mClearCacheObserver == null) {
183            // Lazy instantiation
184            mClearCacheObserver = new CachePackageDataObserver();
185        }
186        mClearingCache = true;
187        try {
188            if (localLOGV) Slog.i(TAG, "Clearing cache");
189            IPackageManager.Stub.asInterface(ServiceManager.getService("package")).
190                    freeStorageAndNotify(mMemLowThreshold, mClearCacheObserver);
191        } catch (RemoteException e) {
192            Slog.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
193            mClearingCache = false;
194            mClearSucceeded = false;
195        }
196    }
197
198    private final void checkMemory(boolean checkCache) {
199        //if the thread that was started to clear cache is still running do nothing till its
200        //finished clearing cache. Ideally this flag could be modified by clearCache
201        // and should be accessed via a lock but even if it does this test will fail now and
202        //hopefully the next time this flag will be set to the correct value.
203        if(mClearingCache) {
204            if(localLOGV) Slog.i(TAG, "Thread already running just skip");
205            //make sure the thread is not hung for too long
206            long diffTime = System.currentTimeMillis() - mThreadStartTime;
207            if(diffTime > (10*60*1000)) {
208                Slog.w(TAG, "Thread that clears cache file seems to run for ever");
209            }
210        } else {
211            restatDataDir();
212            if (localLOGV)  Slog.v(TAG, "freeMemory="+mFreeMem);
213
214            //post intent to NotificationManager to display icon if necessary
215            if (mFreeMem < mMemLowThreshold) {
216                if (!mLowMemFlag) {
217                    if (checkCache) {
218                        // See if clearing cache helps
219                        // Note that clearing cache is asynchronous and so we do a
220                        // memory check again once the cache has been cleared.
221                        mThreadStartTime = System.currentTimeMillis();
222                        mClearSucceeded = false;
223                        clearCache();
224                    } else {
225                        Slog.i(TAG, "Running low on memory. Sending notification");
226                        sendNotification();
227                        mLowMemFlag = true;
228                    }
229                } else {
230                    if (localLOGV) Slog.v(TAG, "Running low on memory " +
231                            "notification already sent. do nothing");
232                }
233            } else {
234                if (mLowMemFlag) {
235                    Slog.i(TAG, "Memory available. Cancelling notification");
236                    cancelNotification();
237                    mLowMemFlag = false;
238                }
239            }
240            if (mFreeMem < mMemFullThreshold) {
241                if (!mMemFullFlag) {
242                    sendFullNotification();
243                    mMemFullFlag = true;
244                }
245            } else {
246                if (mMemFullFlag) {
247                    cancelFullNotification();
248                    mMemFullFlag = false;
249                }
250            }
251        }
252        if(localLOGV) Slog.i(TAG, "Posting Message again");
253        //keep posting messages to itself periodically
254        postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL);
255    }
256
257    private void postCheckMemoryMsg(boolean clearCache, long delay) {
258        // Remove queued messages
259        mHandler.removeMessages(DEVICE_MEMORY_WHAT);
260        mHandler.sendMessageDelayed(mHandler.obtainMessage(DEVICE_MEMORY_WHAT,
261                clearCache ?_TRUE : _FALSE, 0),
262                delay);
263    }
264
265    /*
266     * just query settings to retrieve the memory threshold.
267     * Preferred this over using a ContentObserver since Settings.Secure caches the value
268     * any way
269     */
270    private long getMemThreshold() {
271        long value = Settings.Secure.getInt(
272                              mContentResolver,
273                              Settings.Secure.SYS_STORAGE_THRESHOLD_PERCENTAGE,
274                              DEFAULT_THRESHOLD_PERCENTAGE);
275        if(localLOGV) Slog.v(TAG, "Threshold Percentage="+value);
276        value *= mTotalMemory;
277        long maxValue = Settings.Secure.getInt(
278                mContentResolver,
279                Settings.Secure.SYS_STORAGE_THRESHOLD_MAX_BYTES,
280                DEFAULT_THRESHOLD_MAX_BYTES);
281        //evaluate threshold value
282        return value < maxValue ? value : maxValue;
283    }
284
285    /*
286     * just query settings to retrieve the memory full threshold.
287     * Preferred this over using a ContentObserver since Settings.Secure caches the value
288     * any way
289     */
290    private int getMemFullThreshold() {
291        int value = Settings.Secure.getInt(
292                              mContentResolver,
293                              Settings.Secure.SYS_STORAGE_FULL_THRESHOLD_BYTES,
294                              DEFAULT_FULL_THRESHOLD_BYTES);
295        if(localLOGV) Slog.v(TAG, "Full Threshold Bytes="+value);
296        return value;
297    }
298
299    /**
300    * Constructor to run service. initializes the disk space threshold value
301    * and posts an empty message to kickstart the process.
302    */
303    public DeviceStorageMonitorService(Context context) {
304        mLastReportedFreeMemTime = 0;
305        mContext = context;
306        mContentResolver = mContext.getContentResolver();
307        //create StatFs object
308        mDataFileStats = new StatFs(DATA_PATH);
309        mSystemFileStats = new StatFs(SYSTEM_PATH);
310        mCacheFileStats = new StatFs(CACHE_PATH);
311        //initialize total storage on device
312        mTotalMemory = ((long)mDataFileStats.getBlockCount() *
313                        mDataFileStats.getBlockSize())/100L;
314        mStorageLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW);
315        mStorageLowIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
316        mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK);
317        mStorageOkIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
318        mStorageFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL);
319        mStorageFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
320        mStorageNotFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
321        mStorageNotFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
322        // cache storage thresholds
323        mMemLowThreshold = getMemThreshold();
324        mMemFullThreshold = getMemFullThreshold();
325        checkMemory(true);
326    }
327
328
329    /**
330    * This method sends a notification to NotificationManager to display
331    * an error dialog indicating low disk space and launch the Installer
332    * application
333    */
334    private final void sendNotification() {
335        if(localLOGV) Slog.i(TAG, "Sending low memory notification");
336        //log the event to event log with the amount of free storage(in bytes) left on the device
337        EventLog.writeEvent(EventLogTags.LOW_STORAGE, mFreeMem);
338        //  Pack up the values and broadcast them to everyone
339        Intent lowMemIntent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE);
340        lowMemIntent.putExtra("memory", mFreeMem);
341        lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
342        NotificationManager mNotificationMgr =
343                (NotificationManager)mContext.getSystemService(
344                        Context.NOTIFICATION_SERVICE);
345        CharSequence title = mContext.getText(
346                com.android.internal.R.string.low_internal_storage_view_title);
347        CharSequence details = mContext.getText(
348                com.android.internal.R.string.low_internal_storage_view_text);
349        PendingIntent intent = PendingIntent.getActivity(mContext, 0,  lowMemIntent, 0);
350        Notification notification = new Notification();
351        notification.icon = com.android.internal.R.drawable.stat_notify_disk_full;
352        notification.tickerText = title;
353        notification.flags |= Notification.FLAG_NO_CLEAR;
354        notification.setLatestEventInfo(mContext, title, details, intent);
355        mNotificationMgr.notify(LOW_MEMORY_NOTIFICATION_ID, notification);
356        mContext.sendStickyBroadcast(mStorageLowIntent);
357    }
358
359    /**
360     * Cancels low storage notification and sends OK intent.
361     */
362    private final void cancelNotification() {
363        if(localLOGV) Slog.i(TAG, "Canceling low memory notification");
364        NotificationManager mNotificationMgr =
365                (NotificationManager)mContext.getSystemService(
366                        Context.NOTIFICATION_SERVICE);
367        //cancel notification since memory has been freed
368        mNotificationMgr.cancel(LOW_MEMORY_NOTIFICATION_ID);
369
370        mContext.removeStickyBroadcast(mStorageLowIntent);
371        mContext.sendBroadcast(mStorageOkIntent);
372    }
373
374    /**
375     * Send a notification when storage is full.
376     */
377    private final void sendFullNotification() {
378        if(localLOGV) Slog.i(TAG, "Sending memory full notification");
379        mContext.sendStickyBroadcast(mStorageFullIntent);
380    }
381
382    /**
383     * Cancels memory full notification and sends "not full" intent.
384     */
385    private final void cancelFullNotification() {
386        if(localLOGV) Slog.i(TAG, "Canceling memory full notification");
387        mContext.removeStickyBroadcast(mStorageFullIntent);
388        mContext.sendBroadcast(mStorageNotFullIntent);
389    }
390
391    public void updateMemory() {
392        int callingUid = getCallingUid();
393        if(callingUid != Process.SYSTEM_UID) {
394            return;
395        }
396        // force an early check
397        postCheckMemoryMsg(true, 0);
398    }
399
400    /**
401     * Callable from other things in the system service to obtain the low memory
402     * threshold.
403     *
404     * @return low memory threshold in bytes
405     */
406    public long getMemoryLowThreshold() {
407        return mMemLowThreshold;
408    }
409
410    /**
411     * Callable from other things in the system process to check whether memory
412     * is low.
413     *
414     * @return true is memory is low
415     */
416    public boolean isMemoryLow() {
417        return mLowMemFlag;
418    }
419}
420