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