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