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 { 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 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 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 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 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 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 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 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 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 */ 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(StorageManager.ACTION_MANAGE_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) 473 .setTicker(title) 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() 480 .bigText(details)) 481 .setVisibility(Notification.VISIBILITY_PUBLIC) 482 .setCategory(Notification.CATEGORY_SYSTEM) 483 .build(); 484 notification.flags |= Notification.FLAG_NO_CLEAR; 485 mNotificationMgr.notifyAsUser(null, LOW_MEMORY_NOTIFICATION_ID, notification, 486 UserHandle.ALL); 487 context.sendStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL); 488 } 489 490 /** 491 * Cancels low storage notification and sends OK intent. 492 */ 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); 501 502 context.removeStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL); 503 context.sendBroadcastAsUser(mStorageOkIntent, UserHandle.ALL); 504 } 505 506 /** 507 * Send a notification when storage is full. 508 */ 509 private void sendFullNotification() { 510 if(localLOGV) Slog.i(TAG, "Sending memory full notification"); 511 getContext().sendStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL); 512 } 513 514 /** 515 * Cancels memory full notification and sends "not full" intent. 516 */ 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); 521 } 522 523 private static class CacheFileDeletedObserver extends FileObserver { 524 public CacheFileDeletedObserver() { 525 super(Environment.getDownloadCacheDirectory().getAbsolutePath(), FileObserver.DELETE); 526 } 527 528 @Override 529 public void onEvent(int event, String path) { 530 EventLogTags.writeCacheFileDeleted(path); 531 } 532 } 533 } 534