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