• 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 java.io.FileDescriptor;
20 import java.io.PrintWriter;
21 
22 import android.app.Notification;
23 import android.app.NotificationManager;
24 import android.app.PendingIntent;
25 import android.content.ContentResolver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.pm.IPackageDataObserver;
29 import android.content.pm.IPackageManager;
30 import android.content.pm.PackageManager;
31 import android.os.Binder;
32 import android.os.Environment;
33 import android.os.FileObserver;
34 import android.os.Handler;
35 import android.os.Message;
36 import android.os.Process;
37 import android.os.RemoteException;
38 import android.os.ServiceManager;
39 import android.os.StatFs;
40 import android.os.SystemClock;
41 import android.os.SystemProperties;
42 import android.os.UserHandle;
43 import android.provider.Settings;
44 import android.text.format.Formatter;
45 import android.util.EventLog;
46 import android.util.Slog;
47 import 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  */
67 public 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 {
onRemoveCompleted(String packageName, boolean succeeded)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 
restatDataDir()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 
clearCache()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 
checkMemory(boolean checkCache)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 
postCheckMemoryMsg(boolean clearCache, long delay)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      */
getMemThreshold()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      */
getMemFullThreshold()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     */
DeviceStorageMonitorService(Context context)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     */
sendNotification()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      */
cancelNotification()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      */
sendFullNotification()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      */
cancelFullNotification()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 
updateMemory()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      */
getMemoryLowThreshold()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      */
isMemoryLow()468     public boolean isMemoryLow() {
469         return mLowMemFlag;
470     }
471 
472     public static class CacheFileDeletedObserver extends FileObserver {
CacheFileDeletedObserver()473         public CacheFileDeletedObserver() {
474             super(Environment.getDownloadCacheDirectory().getAbsolutePath(), FileObserver.DELETE);
475         }
476 
477         @Override
onEvent(int event, String path)478         public void onEvent(int event, String path) {
479             EventLogTags.writeCacheFileDeleted(path);
480         }
481     }
482 
483     @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)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