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