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; 18 19 import android.app.Notification; 20 import android.app.NotificationManager; 21 import android.app.PendingIntent; 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.pm.IPackageDataObserver; 26 import android.content.pm.IPackageManager; 27 import android.os.Binder; 28 import android.os.Environment; 29 import android.os.FileObserver; 30 import android.os.Handler; 31 import android.os.Message; 32 import android.os.Process; 33 import android.os.RemoteException; 34 import android.os.ServiceManager; 35 import android.os.StatFs; 36 import android.os.SystemClock; 37 import android.os.SystemProperties; 38 import android.provider.Settings; 39 import android.util.EventLog; 40 import android.util.Slog; 41 42 /** 43 * This class implements a service to monitor the amount of disk 44 * storage space on the device. If the free storage on device is less 45 * than a tunable threshold value (a secure settings parameter; 46 * default 10%) a low memory notification is displayed to alert the 47 * user. If the user clicks on the low memory notification the 48 * Application Manager application gets launched to let the user free 49 * storage space. 50 * 51 * Event log events: A low memory event with the free storage on 52 * device in bytes is logged to the event log when the device goes low 53 * on storage space. The amount of free storage on the device is 54 * periodically logged to the event log. The log interval is a secure 55 * settings parameter with a default value of 12 hours. When the free 56 * storage differential goes below a threshold (again a secure 57 * settings parameter with a default value of 2MB), the free memory is 58 * logged to the event log. 59 */ 60 public class DeviceStorageMonitorService extends Binder { 61 private static final String TAG = "DeviceStorageMonitorService"; 62 private static final boolean DEBUG = false; 63 private static final boolean localLOGV = false; 64 private static final int DEVICE_MEMORY_WHAT = 1; 65 private static final int MONITOR_INTERVAL = 1; //in minutes 66 private static final int LOW_MEMORY_NOTIFICATION_ID = 1; 67 private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10; 68 private static final int DEFAULT_THRESHOLD_MAX_BYTES = 500*1024*1024; // 500MB 69 private static final int DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES = 12*60; //in minutes 70 private static final long DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD = 2 * 1024 * 1024; // 2MB 71 private static final long DEFAULT_CHECK_INTERVAL = MONITOR_INTERVAL*60*1000; 72 private static final int DEFAULT_FULL_THRESHOLD_BYTES = 1024*1024; // 1MB 73 private long mFreeMem; // on /data 74 private long mLastReportedFreeMem; 75 private long mLastReportedFreeMemTime; 76 private boolean mLowMemFlag=false; 77 private boolean mMemFullFlag=false; 78 private Context mContext; 79 private ContentResolver mContentResolver; 80 private long mTotalMemory; // on /data 81 private StatFs mDataFileStats; 82 private StatFs mSystemFileStats; 83 private StatFs mCacheFileStats; 84 private static final String DATA_PATH = "/data"; 85 private static final String SYSTEM_PATH = "/system"; 86 private static final String CACHE_PATH = "/cache"; 87 private long mThreadStartTime = -1; 88 private boolean mClearSucceeded = false; 89 private boolean mClearingCache; 90 private Intent mStorageLowIntent; 91 private Intent mStorageOkIntent; 92 private Intent mStorageFullIntent; 93 private Intent mStorageNotFullIntent; 94 private CachePackageDataObserver mClearCacheObserver; 95 private final CacheFileDeletedObserver mCacheFileDeletedObserver; 96 private static final int _TRUE = 1; 97 private static final int _FALSE = 0; 98 private long mMemLowThreshold; 99 private int mMemFullThreshold; 100 101 /** 102 * This string is used for ServiceManager access to this class. 103 */ 104 public static final String SERVICE = "devicestoragemonitor"; 105 106 /** 107 * Handler that checks the amount of disk space on the device and sends a 108 * notification if the device runs low on disk space 109 */ 110 Handler mHandler = new Handler() { 111 @Override 112 public void handleMessage(Message msg) { 113 //don't handle an invalid message 114 if (msg.what != DEVICE_MEMORY_WHAT) { 115 Slog.e(TAG, "Will not process invalid message"); 116 return; 117 } 118 checkMemory(msg.arg1 == _TRUE); 119 } 120 }; 121 122 class CachePackageDataObserver extends IPackageDataObserver.Stub { 123 public void onRemoveCompleted(String packageName, boolean succeeded) { 124 mClearSucceeded = succeeded; 125 mClearingCache = false; 126 if(localLOGV) Slog.i(TAG, " Clear succeeded:"+mClearSucceeded 127 +", mClearingCache:"+mClearingCache+" Forcing memory check"); 128 postCheckMemoryMsg(false, 0); 129 } 130 } 131 132 private final void restatDataDir() { 133 try { 134 mDataFileStats.restat(DATA_PATH); 135 mFreeMem = (long) mDataFileStats.getAvailableBlocks() * 136 mDataFileStats.getBlockSize(); 137 } catch (IllegalArgumentException e) { 138 // use the old value of mFreeMem 139 } 140 // Allow freemem to be overridden by debug.freemem for testing 141 String debugFreeMem = SystemProperties.get("debug.freemem"); 142 if (!"".equals(debugFreeMem)) { 143 mFreeMem = Long.parseLong(debugFreeMem); 144 } 145 // Read the log interval from secure settings 146 long freeMemLogInterval = Settings.Secure.getLong(mContentResolver, 147 Settings.Secure.SYS_FREE_STORAGE_LOG_INTERVAL, 148 DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES)*60*1000; 149 //log the amount of free memory in event log 150 long currTime = SystemClock.elapsedRealtime(); 151 if((mLastReportedFreeMemTime == 0) || 152 (currTime-mLastReportedFreeMemTime) >= freeMemLogInterval) { 153 mLastReportedFreeMemTime = currTime; 154 long mFreeSystem = -1, mFreeCache = -1; 155 try { 156 mSystemFileStats.restat(SYSTEM_PATH); 157 mFreeSystem = (long) mSystemFileStats.getAvailableBlocks() * 158 mSystemFileStats.getBlockSize(); 159 } catch (IllegalArgumentException e) { 160 // ignore; report -1 161 } 162 try { 163 mCacheFileStats.restat(CACHE_PATH); 164 mFreeCache = (long) mCacheFileStats.getAvailableBlocks() * 165 mCacheFileStats.getBlockSize(); 166 } catch (IllegalArgumentException e) { 167 // ignore; report -1 168 } 169 EventLog.writeEvent(EventLogTags.FREE_STORAGE_LEFT, 170 mFreeMem, mFreeSystem, mFreeCache); 171 } 172 // Read the reporting threshold from secure settings 173 long threshold = Settings.Secure.getLong(mContentResolver, 174 Settings.Secure.DISK_FREE_CHANGE_REPORTING_THRESHOLD, 175 DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD); 176 // If mFree changed significantly log the new value 177 long delta = mFreeMem - mLastReportedFreeMem; 178 if (delta > threshold || delta < -threshold) { 179 mLastReportedFreeMem = mFreeMem; 180 EventLog.writeEvent(EventLogTags.FREE_STORAGE_CHANGED, mFreeMem); 181 } 182 } 183 184 private final void clearCache() { 185 if (mClearCacheObserver == null) { 186 // Lazy instantiation 187 mClearCacheObserver = new CachePackageDataObserver(); 188 } 189 mClearingCache = true; 190 try { 191 if (localLOGV) Slog.i(TAG, "Clearing cache"); 192 IPackageManager.Stub.asInterface(ServiceManager.getService("package")). 193 freeStorageAndNotify(mMemLowThreshold, mClearCacheObserver); 194 } catch (RemoteException e) { 195 Slog.w(TAG, "Failed to get handle for PackageManger Exception: "+e); 196 mClearingCache = false; 197 mClearSucceeded = false; 198 } 199 } 200 201 private final void checkMemory(boolean checkCache) { 202 //if the thread that was started to clear cache is still running do nothing till its 203 //finished clearing cache. Ideally this flag could be modified by clearCache 204 // and should be accessed via a lock but even if it does this test will fail now and 205 //hopefully the next time this flag will be set to the correct value. 206 if(mClearingCache) { 207 if(localLOGV) Slog.i(TAG, "Thread already running just skip"); 208 //make sure the thread is not hung for too long 209 long diffTime = System.currentTimeMillis() - mThreadStartTime; 210 if(diffTime > (10*60*1000)) { 211 Slog.w(TAG, "Thread that clears cache file seems to run for ever"); 212 } 213 } else { 214 restatDataDir(); 215 if (localLOGV) Slog.v(TAG, "freeMemory="+mFreeMem); 216 217 //post intent to NotificationManager to display icon if necessary 218 if (mFreeMem < mMemLowThreshold) { 219 if (!mLowMemFlag) { 220 if (checkCache) { 221 // See if clearing cache helps 222 // Note that clearing cache is asynchronous and so we do a 223 // memory check again once the cache has been cleared. 224 mThreadStartTime = System.currentTimeMillis(); 225 mClearSucceeded = false; 226 clearCache(); 227 } else { 228 Slog.i(TAG, "Running low on memory. Sending notification"); 229 sendNotification(); 230 mLowMemFlag = true; 231 } 232 } else { 233 if (localLOGV) Slog.v(TAG, "Running low on memory " + 234 "notification already sent. do nothing"); 235 } 236 } else { 237 if (mLowMemFlag) { 238 Slog.i(TAG, "Memory available. Cancelling notification"); 239 cancelNotification(); 240 mLowMemFlag = false; 241 } 242 } 243 if (mFreeMem < mMemFullThreshold) { 244 if (!mMemFullFlag) { 245 sendFullNotification(); 246 mMemFullFlag = true; 247 } 248 } else { 249 if (mMemFullFlag) { 250 cancelFullNotification(); 251 mMemFullFlag = false; 252 } 253 } 254 } 255 if(localLOGV) Slog.i(TAG, "Posting Message again"); 256 //keep posting messages to itself periodically 257 postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL); 258 } 259 260 private void postCheckMemoryMsg(boolean clearCache, long delay) { 261 // Remove queued messages 262 mHandler.removeMessages(DEVICE_MEMORY_WHAT); 263 mHandler.sendMessageDelayed(mHandler.obtainMessage(DEVICE_MEMORY_WHAT, 264 clearCache ?_TRUE : _FALSE, 0), 265 delay); 266 } 267 268 /* 269 * just query settings to retrieve the memory threshold. 270 * Preferred this over using a ContentObserver since Settings.Secure caches the value 271 * any way 272 */ 273 private long getMemThreshold() { 274 long value = Settings.Secure.getInt( 275 mContentResolver, 276 Settings.Secure.SYS_STORAGE_THRESHOLD_PERCENTAGE, 277 DEFAULT_THRESHOLD_PERCENTAGE); 278 if(localLOGV) Slog.v(TAG, "Threshold Percentage="+value); 279 value *= mTotalMemory; 280 long maxValue = Settings.Secure.getInt( 281 mContentResolver, 282 Settings.Secure.SYS_STORAGE_THRESHOLD_MAX_BYTES, 283 DEFAULT_THRESHOLD_MAX_BYTES); 284 //evaluate threshold value 285 return value < maxValue ? value : maxValue; 286 } 287 288 /* 289 * just query settings to retrieve the memory full threshold. 290 * Preferred this over using a ContentObserver since Settings.Secure caches the value 291 * any way 292 */ 293 private int getMemFullThreshold() { 294 int value = Settings.Secure.getInt( 295 mContentResolver, 296 Settings.Secure.SYS_STORAGE_FULL_THRESHOLD_BYTES, 297 DEFAULT_FULL_THRESHOLD_BYTES); 298 if(localLOGV) Slog.v(TAG, "Full Threshold Bytes="+value); 299 return value; 300 } 301 302 /** 303 * Constructor to run service. initializes the disk space threshold value 304 * and posts an empty message to kickstart the process. 305 */ 306 public DeviceStorageMonitorService(Context context) { 307 mLastReportedFreeMemTime = 0; 308 mContext = context; 309 mContentResolver = mContext.getContentResolver(); 310 //create StatFs object 311 mDataFileStats = new StatFs(DATA_PATH); 312 mSystemFileStats = new StatFs(SYSTEM_PATH); 313 mCacheFileStats = new StatFs(CACHE_PATH); 314 //initialize total storage on device 315 mTotalMemory = ((long)mDataFileStats.getBlockCount() * 316 mDataFileStats.getBlockSize())/100L; 317 mStorageLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW); 318 mStorageLowIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 319 mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK); 320 mStorageOkIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 321 mStorageFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL); 322 mStorageFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 323 mStorageNotFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL); 324 mStorageNotFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 325 // cache storage thresholds 326 mMemLowThreshold = getMemThreshold(); 327 mMemFullThreshold = getMemFullThreshold(); 328 checkMemory(true); 329 330 mCacheFileDeletedObserver = new CacheFileDeletedObserver(); 331 mCacheFileDeletedObserver.startWatching(); 332 } 333 334 335 /** 336 * This method sends a notification to NotificationManager to display 337 * an error dialog indicating low disk space and launch the Installer 338 * application 339 */ 340 private final void sendNotification() { 341 if(localLOGV) Slog.i(TAG, "Sending low memory notification"); 342 //log the event to event log with the amount of free storage(in bytes) left on the device 343 EventLog.writeEvent(EventLogTags.LOW_STORAGE, mFreeMem); 344 // Pack up the values and broadcast them to everyone 345 Intent lowMemIntent = new Intent(Environment.isExternalStorageEmulated() 346 ? Settings.ACTION_INTERNAL_STORAGE_SETTINGS 347 : Intent.ACTION_MANAGE_PACKAGE_STORAGE); 348 lowMemIntent.putExtra("memory", mFreeMem); 349 lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 350 NotificationManager mNotificationMgr = 351 (NotificationManager)mContext.getSystemService( 352 Context.NOTIFICATION_SERVICE); 353 CharSequence title = mContext.getText( 354 com.android.internal.R.string.low_internal_storage_view_title); 355 CharSequence details = mContext.getText( 356 com.android.internal.R.string.low_internal_storage_view_text); 357 PendingIntent intent = PendingIntent.getActivity(mContext, 0, lowMemIntent, 0); 358 Notification notification = new Notification(); 359 notification.icon = com.android.internal.R.drawable.stat_notify_disk_full; 360 notification.tickerText = title; 361 notification.flags |= Notification.FLAG_NO_CLEAR; 362 notification.setLatestEventInfo(mContext, title, details, intent); 363 mNotificationMgr.notify(LOW_MEMORY_NOTIFICATION_ID, notification); 364 mContext.sendStickyBroadcast(mStorageLowIntent); 365 } 366 367 /** 368 * Cancels low storage notification and sends OK intent. 369 */ 370 private final void cancelNotification() { 371 if(localLOGV) Slog.i(TAG, "Canceling low memory notification"); 372 NotificationManager mNotificationMgr = 373 (NotificationManager)mContext.getSystemService( 374 Context.NOTIFICATION_SERVICE); 375 //cancel notification since memory has been freed 376 mNotificationMgr.cancel(LOW_MEMORY_NOTIFICATION_ID); 377 378 mContext.removeStickyBroadcast(mStorageLowIntent); 379 mContext.sendBroadcast(mStorageOkIntent); 380 } 381 382 /** 383 * Send a notification when storage is full. 384 */ 385 private final void sendFullNotification() { 386 if(localLOGV) Slog.i(TAG, "Sending memory full notification"); 387 mContext.sendStickyBroadcast(mStorageFullIntent); 388 } 389 390 /** 391 * Cancels memory full notification and sends "not full" intent. 392 */ 393 private final void cancelFullNotification() { 394 if(localLOGV) Slog.i(TAG, "Canceling memory full notification"); 395 mContext.removeStickyBroadcast(mStorageFullIntent); 396 mContext.sendBroadcast(mStorageNotFullIntent); 397 } 398 399 public void updateMemory() { 400 int callingUid = getCallingUid(); 401 if(callingUid != Process.SYSTEM_UID) { 402 return; 403 } 404 // force an early check 405 postCheckMemoryMsg(true, 0); 406 } 407 408 /** 409 * Callable from other things in the system service to obtain the low memory 410 * threshold. 411 * 412 * @return low memory threshold in bytes 413 */ 414 public long getMemoryLowThreshold() { 415 return mMemLowThreshold; 416 } 417 418 /** 419 * Callable from other things in the system process to check whether memory 420 * is low. 421 * 422 * @return true is memory is low 423 */ 424 public boolean isMemoryLow() { 425 return mLowMemFlag; 426 } 427 428 public static class CacheFileDeletedObserver extends FileObserver { 429 public CacheFileDeletedObserver() { 430 super(Environment.getDownloadCacheDirectory().getAbsolutePath(), FileObserver.DELETE); 431 } 432 433 @Override 434 public void onEvent(int event, String path) { 435 EventLogTags.writeCacheFileDeleted(path); 436 } 437 } 438 } 439