2 * Copyright (C) 2007-2008 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.server.storage;
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;
51 import java.io.FileDescriptor;
52 import java.io.PrintWriter;
54 import dalvik.system.VMRuntime;
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
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.
74 public class DeviceStorageMonitorService extends SystemService {
75 static final String TAG = "DeviceStorageMonitorService";
77 static final boolean DEBUG = false;
78 static final boolean localLOGV = false;
80 static final int DEVICE_MEMORY_WHAT = 1;
81 private static final int MONITOR_INTERVAL = 1; //in minutes
82 private static final int LOW_MEMORY_NOTIFICATION_ID = 1;
84 private static final int DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES = 12*60; //in minutes
85 private static final long DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD = 2 * 1024 * 1024; // 2MB
86 private static final long DEFAULT_CHECK_INTERVAL = MONITOR_INTERVAL*60*1000;
88 private long mFreeMem; // on /data
89 private long mFreeMemAfterLastCacheClear; // on /data
90 private long mLastReportedFreeMem;
91 private long mLastReportedFreeMemTime;
92 boolean mLowMemFlag=false;
93 private boolean mMemFullFlag=false;
94 private final boolean mIsBootImageOnDisk;
95 private final ContentResolver mResolver;
96 private final long mTotalMemory; // on /data
97 private final StatFs mDataFileStats;
98 private final StatFs mSystemFileStats;
99 private final StatFs mCacheFileStats;
101 private static final File DATA_PATH = Environment.getDataDirectory();
102 private static final File SYSTEM_PATH = Environment.getRootDirectory();
103 private static final File CACHE_PATH = Environment.getDownloadCacheDirectory();
105 private long mThreadStartTime = -1;
106 boolean mClearSucceeded = false;
107 boolean mClearingCache;
108 private final Intent mStorageLowIntent;
109 private final Intent mStorageOkIntent;
110 private final Intent mStorageFullIntent;
111 private final Intent mStorageNotFullIntent;
112 private CachePackageDataObserver mClearCacheObserver;
113 private CacheFileDeletedObserver mCacheFileDeletedObserver;
114 private static final int _TRUE = 1;
115 private static final int _FALSE = 0;
116 // This is the raw threshold that has been set at which we consider
117 // storage to be low.
118 long mMemLowThreshold;
119 // This is the threshold at which we start trying to flush caches
120 // to get below the low threshold limit. It is less than the low
121 // threshold; we will allow storage to get a bit beyond the limit
122 // before flushing and checking if we are actually low.
123 private long mMemCacheStartTrimThreshold;
124 // This is the threshold that we try to get to when deleting cache
125 // files. This is greater than the low threshold so that we will flush
126 // more files than absolutely needed, to reduce the frequency that
127 // flushing takes place.
128 private long mMemCacheTrimToThreshold;
129 private long mMemFullThreshold;
132 * This string is used for ServiceManager access to this class.
134 static final String SERVICE = "devicestoragemonitor";
137 * Handler that checks the amount of disk space on the device and sends a
138 * notification if the device runs low on disk space
140 private final Handler mHandler = new Handler() {
142 public void handleMessage(Message msg) {
143 //don't handle an invalid message
144 if (msg.what != DEVICE_MEMORY_WHAT) {
145 Slog.e(TAG, "Will not process invalid message");
148 checkMemory(msg.arg1 == _TRUE);
152 private class CachePackageDataObserver extends IPackageDataObserver.Stub {
153 public void onRemoveCompleted(String packageName, boolean succeeded) {
154 mClearSucceeded = succeeded;
155 mClearingCache = false;
156 if(localLOGV) Slog.i(TAG, " Clear succeeded:"+mClearSucceeded
157 +", mClearingCache:"+mClearingCache+" Forcing memory check");
158 postCheckMemoryMsg(false, 0);
162 private void restatDataDir() {
164 mDataFileStats.restat(DATA_PATH.getAbsolutePath());
165 mFreeMem = (long) mDataFileStats.getAvailableBlocks() *
166 mDataFileStats.getBlockSize();
167 } catch (IllegalArgumentException e) {
168 // use the old value of mFreeMem
170 // Allow freemem to be overridden by debug.freemem for testing
171 String debugFreeMem = SystemProperties.get("debug.freemem");
172 if (!"".equals(debugFreeMem)) {
173 mFreeMem = Long.parseLong(debugFreeMem);
175 // Read the log interval from secure settings
176 long freeMemLogInterval = Settings.Global.getLong(mResolver,
177 Settings.Global.SYS_FREE_STORAGE_LOG_INTERVAL,
178 DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES)*60*1000;
179 //log the amount of free memory in event log
180 long currTime = SystemClock.elapsedRealtime();
181 if((mLastReportedFreeMemTime == 0) ||
182 (currTime-mLastReportedFreeMemTime) >= freeMemLogInterval) {
183 mLastReportedFreeMemTime = currTime;
184 long mFreeSystem = -1, mFreeCache = -1;
186 mSystemFileStats.restat(SYSTEM_PATH.getAbsolutePath());
187 mFreeSystem = (long) mSystemFileStats.getAvailableBlocks() *
188 mSystemFileStats.getBlockSize();
189 } catch (IllegalArgumentException e) {
193 mCacheFileStats.restat(CACHE_PATH.getAbsolutePath());
194 mFreeCache = (long) mCacheFileStats.getAvailableBlocks() *
195 mCacheFileStats.getBlockSize();
196 } catch (IllegalArgumentException e) {
199 EventLog.writeEvent(EventLogTags.FREE_STORAGE_LEFT,
200 mFreeMem, mFreeSystem, mFreeCache);
202 // Read the reporting threshold from secure settings
203 long threshold = Settings.Global.getLong(mResolver,
204 Settings.Global.DISK_FREE_CHANGE_REPORTING_THRESHOLD,
205 DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD);
206 // If mFree changed significantly log the new value
207 long delta = mFreeMem - mLastReportedFreeMem;
208 if (delta > threshold || delta < -threshold) {
209 mLastReportedFreeMem = mFreeMem;
210 EventLog.writeEvent(EventLogTags.FREE_STORAGE_CHANGED, mFreeMem);
214 private void clearCache() {
215 if (mClearCacheObserver == null) {
216 // Lazy instantiation
217 mClearCacheObserver = new CachePackageDataObserver();
219 mClearingCache = true;
221 if (localLOGV) Slog.i(TAG, "Clearing cache");
222 IPackageManager.Stub.asInterface(ServiceManager.getService("package")).
223 freeStorageAndNotify(mMemCacheTrimToThreshold, mClearCacheObserver);
224 } catch (RemoteException e) {
225 Slog.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
226 mClearingCache = false;
227 mClearSucceeded = false;
231 void checkMemory(boolean checkCache) {
232 //if the thread that was started to clear cache is still running do nothing till its
233 //finished clearing cache. Ideally this flag could be modified by clearCache
234 // and should be accessed via a lock but even if it does this test will fail now and
235 //hopefully the next time this flag will be set to the correct value.
237 if(localLOGV) Slog.i(TAG, "Thread already running just skip");
238 //make sure the thread is not hung for too long
239 long diffTime = System.currentTimeMillis() - mThreadStartTime;
240 if(diffTime > (10*60*1000)) {
241 Slog.w(TAG, "Thread that clears cache file seems to run for ever");
245 if (localLOGV) Slog.v(TAG, "freeMemory="+mFreeMem);
247 //post intent to NotificationManager to display icon if necessary
248 if (mFreeMem < mMemLowThreshold) {
250 // We are allowed to clear cache files at this point to
251 // try to get down below the limit, because this is not
252 // the initial call after a cache clear has been attempted.
253 // In this case we will try a cache clear if our free
254 // space has gone below the cache clear limit.
255 if (mFreeMem < mMemCacheStartTrimThreshold) {
256 // We only clear the cache if the free storage has changed
257 // a significant amount since the last time.
258 if ((mFreeMemAfterLastCacheClear-mFreeMem)
259 >= ((mMemLowThreshold-mMemCacheStartTrimThreshold)/4)) {
260 // See if clearing cache helps
261 // Note that clearing cache is asynchronous and so we do a
262 // memory check again once the cache has been cleared.
263 mThreadStartTime = System.currentTimeMillis();
264 mClearSucceeded = false;
269 // This is a call from after clearing the cache. Note
270 // the amount of free storage at this point.
271 mFreeMemAfterLastCacheClear = mFreeMem;
273 // We tried to clear the cache, but that didn't get us
274 // below the low storage limit. Tell the user.
275 Slog.i(TAG, "Running low on memory. Sending notification");
279 if (localLOGV) Slog.v(TAG, "Running low on memory " +
280 "notification already sent. do nothing");
284 mFreeMemAfterLastCacheClear = mFreeMem;
286 Slog.i(TAG, "Memory available. Cancelling notification");
287 cancelNotification();
291 if (!mLowMemFlag && !mIsBootImageOnDisk) {
292 Slog.i(TAG, "No boot image on disk due to lack of space. Sending notification");
295 if (mFreeMem < mMemFullThreshold) {
297 sendFullNotification();
302 cancelFullNotification();
303 mMemFullFlag = false;
307 if(localLOGV) Slog.i(TAG, "Posting Message again");
308 //keep posting messages to itself periodically
309 postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL);
312 void postCheckMemoryMsg(boolean clearCache, long delay) {
313 // Remove queued messages
314 mHandler.removeMessages(DEVICE_MEMORY_WHAT);
315 mHandler.sendMessageDelayed(mHandler.obtainMessage(DEVICE_MEMORY_WHAT,
316 clearCache ?_TRUE : _FALSE, 0),
320 public DeviceStorageMonitorService(Context context) {
322 mLastReportedFreeMemTime = 0;
323 mResolver = context.getContentResolver();
324 mIsBootImageOnDisk = isBootImageOnDisk();
325 //create StatFs object
326 mDataFileStats = new StatFs(DATA_PATH.getAbsolutePath());
327 mSystemFileStats = new StatFs(SYSTEM_PATH.getAbsolutePath());
328 mCacheFileStats = new StatFs(CACHE_PATH.getAbsolutePath());
329 //initialize total storage on device
330 mTotalMemory = (long)mDataFileStats.getBlockCount() *
331 mDataFileStats.getBlockSize();
332 mStorageLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW);
333 mStorageLowIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
334 mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK);
335 mStorageOkIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
336 mStorageFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL);
337 mStorageFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
338 mStorageNotFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
339 mStorageNotFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
342 private static boolean isBootImageOnDisk() {
343 for (String instructionSet : InstructionSets.getAllDexCodeInstructionSets()) {
344 if (!VMRuntime.isBootClassPathOnDisk(instructionSet)) {
352 * Initializes the disk space threshold value and posts an empty message to
353 * kickstart the process.
356 public void onStart() {
357 // cache storage thresholds
358 final StorageManager sm = StorageManager.from(getContext());
359 mMemLowThreshold = sm.getStorageLowBytes(DATA_PATH);
360 mMemFullThreshold = sm.getStorageFullBytes(DATA_PATH);
362 mMemCacheStartTrimThreshold = ((mMemLowThreshold*3)+mMemFullThreshold)/4;
363 mMemCacheTrimToThreshold = mMemLowThreshold
364 + ((mMemLowThreshold-mMemCacheStartTrimThreshold)*2);
365 mFreeMemAfterLastCacheClear = mTotalMemory;
368 mCacheFileDeletedObserver = new CacheFileDeletedObserver();
369 mCacheFileDeletedObserver.startWatching();
371 publishBinderService(SERVICE, mRemoteService);
372 publishLocalService(DeviceStorageMonitorInternal.class, mLocalService);
375 private final DeviceStorageMonitorInternal mLocalService = new DeviceStorageMonitorInternal() {
377 public void checkMemory() {
378 // force an early check
379 postCheckMemoryMsg(true, 0);
383 public boolean isMemoryLow() {
384 return mLowMemFlag || !mIsBootImageOnDisk;
388 public long getMemoryLowThreshold() {
389 return mMemLowThreshold;
393 private final IBinder mRemoteService = new Binder() {
395 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
396 if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
397 != PackageManager.PERMISSION_GRANTED) {
399 pw.println("Permission Denial: can't dump " + SERVICE + " from from pid="
400 + Binder.getCallingPid()
401 + ", uid=" + Binder.getCallingUid());
409 void dumpImpl(PrintWriter pw) {
410 final Context context = getContext();
412 pw.println("Current DeviceStorageMonitor state:");
414 pw.print(" mFreeMem="); pw.print(Formatter.formatFileSize(context, mFreeMem));
415 pw.print(" mTotalMemory=");
416 pw.println(Formatter.formatFileSize(context, mTotalMemory));
418 pw.print(" mFreeMemAfterLastCacheClear=");
419 pw.println(Formatter.formatFileSize(context, mFreeMemAfterLastCacheClear));
421 pw.print(" mLastReportedFreeMem=");
422 pw.print(Formatter.formatFileSize(context, mLastReportedFreeMem));
423 pw.print(" mLastReportedFreeMemTime=");
424 TimeUtils.formatDuration(mLastReportedFreeMemTime, SystemClock.elapsedRealtime(), pw);
427 pw.print(" mLowMemFlag="); pw.print(mLowMemFlag);
428 pw.print(" mMemFullFlag="); pw.println(mMemFullFlag);
429 pw.print(" mIsBootImageOnDisk="); pw.print(mIsBootImageOnDisk);
431 pw.print(" mClearSucceeded="); pw.print(mClearSucceeded);
432 pw.print(" mClearingCache="); pw.println(mClearingCache);
434 pw.print(" mMemLowThreshold=");
435 pw.print(Formatter.formatFileSize(context, mMemLowThreshold));
436 pw.print(" mMemFullThreshold=");
437 pw.println(Formatter.formatFileSize(context, mMemFullThreshold));
439 pw.print(" mMemCacheStartTrimThreshold=");
440 pw.print(Formatter.formatFileSize(context, mMemCacheStartTrimThreshold));
441 pw.print(" mMemCacheTrimToThreshold=");
442 pw.println(Formatter.formatFileSize(context, mMemCacheTrimToThreshold));
446 * This method sends a notification to NotificationManager to display
447 * an error dialog indicating low disk space and launch the Installer
450 private void sendNotification() {
451 final Context context = getContext();
452 if(localLOGV) Slog.i(TAG, "Sending low memory notification");
453 //log the event to event log with the amount of free storage(in bytes) left on the device
454 EventLog.writeEvent(EventLogTags.LOW_STORAGE, mFreeMem);
455 // Pack up the values and broadcast them to everyone
456 Intent lowMemIntent = new Intent(Environment.isExternalStorageEmulated()
457 ? Settings.ACTION_INTERNAL_STORAGE_SETTINGS
458 : Intent.ACTION_MANAGE_PACKAGE_STORAGE);
459 lowMemIntent.putExtra("memory", mFreeMem);
460 lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
461 NotificationManager mNotificationMgr =
462 (NotificationManager)context.getSystemService(
463 Context.NOTIFICATION_SERVICE);
464 CharSequence title = context.getText(
465 com.android.internal.R.string.low_internal_storage_view_title);
466 CharSequence details = context.getText(mIsBootImageOnDisk
467 ? com.android.internal.R.string.low_internal_storage_view_text
468 : com.android.internal.R.string.low_internal_storage_view_text_no_boot);
469 PendingIntent intent = PendingIntent.getActivityAsUser(context, 0, lowMemIntent, 0,
470 null, UserHandle.CURRENT);
471 Notification notification = new Notification.Builder(context)
472 .setSmallIcon(com.android.internal.R.drawable.stat_notify_disk_full)
474 .setColor(context.getColor(
475 com.android.internal.R.color.system_notification_accent_color))
476 .setContentTitle(title)
477 .setContentText(details)
478 .setContentIntent(intent)
479 .setStyle(new Notification.BigTextStyle()
481 .setVisibility(Notification.VISIBILITY_PUBLIC)
482 .setCategory(Notification.CATEGORY_SYSTEM)
484 notification.flags |= Notification.FLAG_NO_CLEAR;
485 mNotificationMgr.notifyAsUser(null, LOW_MEMORY_NOTIFICATION_ID, notification,
487 context.sendStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
491 * Cancels low storage notification and sends OK intent.
493 private void cancelNotification() {
494 final Context context = getContext();
495 if(localLOGV) Slog.i(TAG, "Canceling low memory notification");
496 NotificationManager mNotificationMgr =
497 (NotificationManager)context.getSystemService(
498 Context.NOTIFICATION_SERVICE);
499 //cancel notification since memory has been freed
500 mNotificationMgr.cancelAsUser(null, LOW_MEMORY_NOTIFICATION_ID, UserHandle.ALL);
502 context.removeStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
503 context.sendBroadcastAsUser(mStorageOkIntent, UserHandle.ALL);
507 * Send a notification when storage is full.
509 private void sendFullNotification() {
510 if(localLOGV) Slog.i(TAG, "Sending memory full notification");
511 getContext().sendStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL);
515 * Cancels memory full notification and sends "not full" intent.
517 private void cancelFullNotification() {
518 if(localLOGV) Slog.i(TAG, "Canceling memory full notification");
519 getContext().removeStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL);
520 getContext().sendBroadcastAsUser(mStorageNotFullIntent, UserHandle.ALL);
523 private static class CacheFileDeletedObserver extends FileObserver {
524 public CacheFileDeletedObserver() {
525 super(Environment.getDownloadCacheDirectory().getAbsolutePath(), FileObserver.DELETE);
529 public void onEvent(int event, String path) {
530 EventLogTags.writeCacheFileDeleted(path);