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