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