• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.server;
18 
19 import android.app.Notification;
20 import android.app.NotificationManager;
21 import android.app.PendingIntent;
22 import android.content.ContentResolver;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.pm.IPackageDataObserver;
26 import android.content.pm.IPackageManager;
27 import android.os.Binder;
28 import android.os.Handler;
29 import android.os.Message;
30 import android.os.Process;
31 import android.os.RemoteException;
32 import android.os.ServiceManager;
33 import android.os.StatFs;
34 import android.os.SystemClock;
35 import android.os.SystemProperties;
36 import android.provider.Settings;
37 import android.util.EventLog;
38 import 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  */
58 public 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 {
onRemoveCompleted(String packageName, boolean succeeded)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 
restatDataDir()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             mCacheFileStats.restat(CACHE_PATH);
167             EventLog.writeEvent(EventLogTags.FREE_STORAGE_LEFT,
168                                 mFreeMem, mFreeSystem, mFreeCache);
169         }
170         // Read the reporting threshold from secure settings
171         long threshold = Settings.Secure.getLong(mContentResolver,
172                 Settings.Secure.DISK_FREE_CHANGE_REPORTING_THRESHOLD,
173                 DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD);
174         // If mFree changed significantly log the new value
175         long delta = mFreeMem - mLastReportedFreeMem;
176         if (delta > threshold || delta < -threshold) {
177             mLastReportedFreeMem = mFreeMem;
178             EventLog.writeEvent(EventLogTags.FREE_STORAGE_CHANGED, mFreeMem);
179         }
180     }
181 
clearCache()182     private final void clearCache() {
183         if (mClearCacheObserver == null) {
184             // Lazy instantiation
185             mClearCacheObserver = new CachePackageDataObserver();
186         }
187         mClearingCache = true;
188         try {
189             if (localLOGV) Slog.i(TAG, "Clearing cache");
190             IPackageManager.Stub.asInterface(ServiceManager.getService("package")).
191                     freeStorageAndNotify(mMemLowThreshold, mClearCacheObserver);
192         } catch (RemoteException e) {
193             Slog.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
194             mClearingCache = false;
195             mClearSucceeded = false;
196         }
197     }
198 
checkMemory(boolean checkCache)199     private final void checkMemory(boolean checkCache) {
200         //if the thread that was started to clear cache is still running do nothing till its
201         //finished clearing cache. Ideally this flag could be modified by clearCache
202         // and should be accessed via a lock but even if it does this test will fail now and
203         //hopefully the next time this flag will be set to the correct value.
204         if(mClearingCache) {
205             if(localLOGV) Slog.i(TAG, "Thread already running just skip");
206             //make sure the thread is not hung for too long
207             long diffTime = System.currentTimeMillis() - mThreadStartTime;
208             if(diffTime > (10*60*1000)) {
209                 Slog.w(TAG, "Thread that clears cache file seems to run for ever");
210             }
211         } else {
212             restatDataDir();
213             if (localLOGV)  Slog.v(TAG, "freeMemory="+mFreeMem);
214 
215             //post intent to NotificationManager to display icon if necessary
216             if (mFreeMem < mMemLowThreshold) {
217                 if (!mLowMemFlag) {
218                     if (checkCache) {
219                         // See if clearing cache helps
220                         // Note that clearing cache is asynchronous and so we do a
221                         // memory check again once the cache has been cleared.
222                         mThreadStartTime = System.currentTimeMillis();
223                         mClearSucceeded = false;
224                         clearCache();
225                     } else {
226                         Slog.i(TAG, "Running low on memory. Sending notification");
227                         sendNotification();
228                         mLowMemFlag = true;
229                     }
230                 } else {
231                     if (localLOGV) Slog.v(TAG, "Running low on memory " +
232                             "notification already sent. do nothing");
233                 }
234             } else {
235                 if (mLowMemFlag) {
236                     Slog.i(TAG, "Memory available. Cancelling notification");
237                     cancelNotification();
238                     mLowMemFlag = false;
239                 }
240             }
241             if (mFreeMem < mMemFullThreshold) {
242                 if (!mMemFullFlag) {
243                     sendFullNotification();
244                     mMemFullFlag = true;
245                 }
246             } else {
247                 if (mMemFullFlag) {
248                     cancelFullNotification();
249                     mMemFullFlag = false;
250                 }
251             }
252         }
253         if(localLOGV) Slog.i(TAG, "Posting Message again");
254         //keep posting messages to itself periodically
255         postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL);
256     }
257 
postCheckMemoryMsg(boolean clearCache, long delay)258     private void postCheckMemoryMsg(boolean clearCache, long delay) {
259         // Remove queued messages
260         mHandler.removeMessages(DEVICE_MEMORY_WHAT);
261         mHandler.sendMessageDelayed(mHandler.obtainMessage(DEVICE_MEMORY_WHAT,
262                 clearCache ?_TRUE : _FALSE, 0),
263                 delay);
264     }
265 
266     /*
267      * just query settings to retrieve the memory threshold.
268      * Preferred this over using a ContentObserver since Settings.Secure caches the value
269      * any way
270      */
getMemThreshold()271     private long getMemThreshold() {
272         long value = Settings.Secure.getInt(
273                               mContentResolver,
274                               Settings.Secure.SYS_STORAGE_THRESHOLD_PERCENTAGE,
275                               DEFAULT_THRESHOLD_PERCENTAGE);
276         if(localLOGV) Slog.v(TAG, "Threshold Percentage="+value);
277         value *= mTotalMemory;
278         long maxValue = Settings.Secure.getInt(
279                 mContentResolver,
280                 Settings.Secure.SYS_STORAGE_THRESHOLD_MAX_BYTES,
281                 DEFAULT_THRESHOLD_MAX_BYTES);
282         //evaluate threshold value
283         return value < maxValue ? value : maxValue;
284     }
285 
286     /*
287      * just query settings to retrieve the memory full threshold.
288      * Preferred this over using a ContentObserver since Settings.Secure caches the value
289      * any way
290      */
getMemFullThreshold()291     private int getMemFullThreshold() {
292         int value = Settings.Secure.getInt(
293                               mContentResolver,
294                               Settings.Secure.SYS_STORAGE_FULL_THRESHOLD_BYTES,
295                               DEFAULT_FULL_THRESHOLD_BYTES);
296         if(localLOGV) Slog.v(TAG, "Full Threshold Bytes="+value);
297         return value;
298     }
299 
300     /**
301     * Constructor to run service. initializes the disk space threshold value
302     * and posts an empty message to kickstart the process.
303     */
DeviceStorageMonitorService(Context context)304     public DeviceStorageMonitorService(Context context) {
305         mLastReportedFreeMemTime = 0;
306         mContext = context;
307         mContentResolver = mContext.getContentResolver();
308         //create StatFs object
309         mDataFileStats = new StatFs(DATA_PATH);
310         mSystemFileStats = new StatFs(SYSTEM_PATH);
311         mCacheFileStats = new StatFs(CACHE_PATH);
312         //initialize total storage on device
313         mTotalMemory = ((long)mDataFileStats.getBlockCount() *
314                         mDataFileStats.getBlockSize())/100L;
315         mStorageLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW);
316         mStorageLowIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
317         mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK);
318         mStorageOkIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
319         mStorageFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL);
320         mStorageFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
321         mStorageNotFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
322         mStorageNotFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
323         // cache storage thresholds
324         mMemLowThreshold = getMemThreshold();
325         mMemFullThreshold = getMemFullThreshold();
326         checkMemory(true);
327     }
328 
329 
330     /**
331     * This method sends a notification to NotificationManager to display
332     * an error dialog indicating low disk space and launch the Installer
333     * application
334     */
sendNotification()335     private final void sendNotification() {
336         if(localLOGV) Slog.i(TAG, "Sending low memory notification");
337         //log the event to event log with the amount of free storage(in bytes) left on the device
338         EventLog.writeEvent(EventLogTags.LOW_STORAGE, mFreeMem);
339         //  Pack up the values and broadcast them to everyone
340         Intent lowMemIntent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE);
341         lowMemIntent.putExtra("memory", mFreeMem);
342         lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
343         NotificationManager mNotificationMgr =
344                 (NotificationManager)mContext.getSystemService(
345                         Context.NOTIFICATION_SERVICE);
346         CharSequence title = mContext.getText(
347                 com.android.internal.R.string.low_internal_storage_view_title);
348         CharSequence details = mContext.getText(
349                 com.android.internal.R.string.low_internal_storage_view_text);
350         PendingIntent intent = PendingIntent.getActivity(mContext, 0,  lowMemIntent, 0);
351         Notification notification = new Notification();
352         notification.icon = com.android.internal.R.drawable.stat_notify_disk_full;
353         notification.tickerText = title;
354         notification.flags |= Notification.FLAG_NO_CLEAR;
355         notification.setLatestEventInfo(mContext, title, details, intent);
356         mNotificationMgr.notify(LOW_MEMORY_NOTIFICATION_ID, notification);
357         mContext.sendStickyBroadcast(mStorageLowIntent);
358     }
359 
360     /**
361      * Cancels low storage notification and sends OK intent.
362      */
cancelNotification()363     private final void cancelNotification() {
364         if(localLOGV) Slog.i(TAG, "Canceling low memory notification");
365         NotificationManager mNotificationMgr =
366                 (NotificationManager)mContext.getSystemService(
367                         Context.NOTIFICATION_SERVICE);
368         //cancel notification since memory has been freed
369         mNotificationMgr.cancel(LOW_MEMORY_NOTIFICATION_ID);
370 
371         mContext.removeStickyBroadcast(mStorageLowIntent);
372         mContext.sendBroadcast(mStorageOkIntent);
373     }
374 
375     /**
376      * Send a notification when storage is full.
377      */
sendFullNotification()378     private final void sendFullNotification() {
379         if(localLOGV) Slog.i(TAG, "Sending memory full notification");
380         mContext.sendStickyBroadcast(mStorageFullIntent);
381     }
382 
383     /**
384      * Cancels memory full notification and sends "not full" intent.
385      */
cancelFullNotification()386     private final void cancelFullNotification() {
387         if(localLOGV) Slog.i(TAG, "Canceling memory full notification");
388         mContext.removeStickyBroadcast(mStorageFullIntent);
389         mContext.sendBroadcast(mStorageNotFullIntent);
390     }
391 
updateMemory()392     public void updateMemory() {
393         int callingUid = getCallingUid();
394         if(callingUid != Process.SYSTEM_UID) {
395             return;
396         }
397         // force an early check
398         postCheckMemoryMsg(true, 0);
399     }
400 
401     /**
402      * Callable from other things in the system service to obtain the low memory
403      * threshold.
404      *
405      * @return low memory threshold in bytes
406      */
getMemoryLowThreshold()407     public long getMemoryLowThreshold() {
408         return mMemLowThreshold;
409     }
410 
411     /**
412      * Callable from other things in the system process to check whether memory
413      * is low.
414      *
415      * @return true is memory is low
416      */
isMemoryLow()417     public boolean isMemoryLow() {
418         return mLowMemFlag;
419     }
420 }
421