blob: 85861bbfe81a70d2e3efbcf3863a65e0549d78da [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
46 * (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
48 * 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
66 private static final int EVENT_LOG_STORAGE_BELOW_THRESHOLD = 2744;
67 private static final int EVENT_LOG_LOW_STORAGE_NOTIFICATION = 2745;
68 private static final int EVENT_LOG_FREE_STORAGE_LEFT = 2746;
69 private static final long DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD = 2 * 1024 * 1024; // 2MB
70 private static final long DEFAULT_CHECK_INTERVAL = MONITOR_INTERVAL*60*1000;
71 private long mFreeMem;
72 private long mLastReportedFreeMem;
73 private long mLastReportedFreeMemTime;
74 private boolean mLowMemFlag=false;
75 private Context mContext;
76 private ContentResolver mContentResolver;
77 int mBlkSize;
78 long mTotalMemory;
79 StatFs mFileStats;
80 private static final String DATA_PATH="/data";
81 long mThreadStartTime = -1;
82 boolean mClearSucceeded = false;
83 boolean mClearingCache;
84 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;
89
90 /**
91 * This string is used for ServiceManager access to this class.
92 */
93 static final String SERVICE = "devicestoragemonitor";
94
95 /**
96 * Handler that checks the amount of disk space on the device and sends a
97 * 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 };
110
111 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);
118 }
119 }
120
121 private final void restatDataDir() {
122 mFileStats.restat(DATA_PATH);
123 mFreeMem = mFileStats.getAvailableBlocks()*mBlkSize;
124 // Allow freemem to be overridden by debug.freemem for testing
125 String debugFreeMem = SystemProperties.get("debug.freemem");
126 if (!"".equals(debugFreeMem)) {
127 mFreeMem = Long.parseLong(debugFreeMem);
128 }
129 // Read the log interval from Gservices
130 long freeMemLogInterval = Gservices.getLong(mContentResolver,
131 Gservices.SYS_FREE_STORAGE_LOG_INTERVAL,
132 DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES)*60*1000;
133 //log the amount of free memory in event log
134 long currTime = SystemClock.elapsedRealtime();
135 if((mLastReportedFreeMemTime == 0) ||
136 (currTime-mLastReportedFreeMemTime) >= freeMemLogInterval) {
137 mLastReportedFreeMemTime = currTime;
138 EventLog.writeEvent(EVENT_LOG_FREE_STORAGE_LEFT, mFreeMem);
139 }
140 // Read the reporting threshold from Gservices
141 long threshold = Gservices.getLong(mContentResolver,
142 Gservices.DISK_FREE_CHANGE_REPORTING_THRESHOLD,
143 DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD);
144 // If mFree changed significantly log the new value
145 long delta = mFreeMem - mLastReportedFreeMem;
146 if (delta > threshold || delta < -threshold) {
147 mLastReportedFreeMem = mFreeMem;
148 EventLog.writeEvent(EVENT_LOG_STORAGE_BELOW_THRESHOLD, mFreeMem);
149 }
150 }
151
152 private final void clearCache() {
153 if (mClearCacheObserver == null) {
154 // Lazy instantiation
155 mClearCacheObserver = new CachePackageDataObserver();
156 }
157 mClearingCache = true;
158 try {
159 if (localLOGV) Log.i(TAG, "Clearing cache");
160 IPackageManager.Stub.asInterface(ServiceManager.getService("package")).
161 freeStorageAndNotify(getMemThreshold(), mClearCacheObserver);
162 } catch (RemoteException e) {
163 Log.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
164 mClearingCache = false;
165 mClearSucceeded = false;
166 }
167 }
168
169 private final void checkMemory(boolean checkCache) {
170 //if the thread that was started to clear cache is still running do nothing till its
171 //finished clearing cache. Ideally this flag could be modified by clearCache
172 // and should be accessed via a lock but even if it does this test will fail now and
173 //hopefully the next time this flag will be set to the correct value.
174 if(mClearingCache) {
175 if(localLOGV) Log.i(TAG, "Thread already running just skip");
176 //make sure the thread is not hung for too long
177 long diffTime = System.currentTimeMillis() - mThreadStartTime;
178 if(diffTime > (10*60*1000)) {
179 Log.w(TAG, "Thread that clears cache file seems to run for ever");
180 }
181 } else {
182 restatDataDir();
183 if (localLOGV) Log.v(TAG, "freeMemory="+mFreeMem);
184
185 //post intent to NotificationManager to display icon if necessary
186 long memThreshold = getMemThreshold();
187 if (mFreeMem < memThreshold) {
188 if (!mLowMemFlag) {
189 if (checkCache) {
190 // See if clearing cache helps
191 // Note that clearing cache is asynchronous and so we do a
192 // memory check again once the cache has been cleared.
193 mThreadStartTime = System.currentTimeMillis();
194 mClearSucceeded = false;
195 clearCache();
196 } else {
197 Log.i(TAG, "Running low on memory. Sending notification");
198 sendNotification();
199 mLowMemFlag = true;
200 }
201 } else {
202 if (localLOGV) Log.v(TAG, "Running low on memory " +
203 "notification already sent. do nothing");
204 }
205 } else {
206 if (mLowMemFlag) {
207 Log.i(TAG, "Memory available. Cancelling notification");
208 cancelNotification();
209 mLowMemFlag = false;
210 }
211 }
212 }
213 if(localLOGV) Log.i(TAG, "Posting Message again");
214 //keep posting messages to itself periodically
215 postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL);
216 }
217
218 private void postCheckMemoryMsg(boolean clearCache, long delay) {
219 // Remove queued messages
220 mHandler.removeMessages(DEVICE_MEMORY_WHAT);
221 mHandler.sendMessageDelayed(mHandler.obtainMessage(DEVICE_MEMORY_WHAT,
222 clearCache ?_TRUE : _FALSE, 0),
223 delay);
224 }
225
226 /*
227 * just query settings to retrieve the memory threshold.
228 * Preferred this over using a ContentObserver since Settings.Gservices caches the value
229 * any way
230 */
231 private long getMemThreshold() {
232 int value = Settings.Gservices.getInt(
233 mContentResolver,
234 Settings.Gservices.SYS_STORAGE_THRESHOLD_PERCENTAGE,
235 DEFAULT_THRESHOLD_PERCENTAGE);
236 if(localLOGV) Log.v(TAG, "Threshold Percentage="+value);
237 //evaluate threshold value
238 return mTotalMemory*value;
239 }
240
241 /**
242 * Constructor to run service. initializes the disk space threshold value
243 * and posts an empty message to kickstart the process.
244 */
245 public DeviceStorageMonitorService(Context context) {
246 mLastReportedFreeMemTime = 0;
247 mContext = context;
248 mContentResolver = mContext.getContentResolver();
249 //create StatFs object
250 mFileStats = new StatFs(DATA_PATH);
251 //initialize block size
252 mBlkSize = mFileStats.getBlockSize();
253 //initialize total storage on device
254 mTotalMemory = (mFileStats.getBlockCount()*mBlkSize)/100;
255 mStorageLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW);
256 mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK);
257 checkMemory(true);
258 }
259
260
261 /**
262 * This method sends a notification to NotificationManager to display
263 * an error dialog indicating low disk space and launch the Installer
264 * application
265 */
266 private final void sendNotification() {
267 if(localLOGV) Log.i(TAG, "Sending low memory notification");
268 //log the event to event log with the amount of free storage(in bytes) left on the device
269 EventLog.writeEvent(EVENT_LOG_LOW_STORAGE_NOTIFICATION, mFreeMem);
270 // Pack up the values and broadcast them to everyone
271 Intent lowMemIntent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE);
272 lowMemIntent.putExtra("memory", mFreeMem);
273 lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
274 NotificationManager mNotificationMgr =
275 (NotificationManager)mContext.getSystemService(
276 Context.NOTIFICATION_SERVICE);
277 CharSequence title = mContext.getText(
278 com.android.internal.R.string.low_internal_storage_view_title);
279 CharSequence details = mContext.getText(
280 com.android.internal.R.string.low_internal_storage_view_text);
281 PendingIntent intent = PendingIntent.getActivity(mContext, 0, lowMemIntent, 0);
282 Notification notification = new Notification();
283 notification.icon = com.android.internal.R.drawable.stat_notify_disk_full;
284 notification.tickerText = title;
285 notification.flags |= Notification.FLAG_NO_CLEAR;
286 notification.setLatestEventInfo(mContext, title, details, intent);
287 mNotificationMgr.notify(LOW_MEMORY_NOTIFICATION_ID, notification);
288 mContext.sendStickyBroadcast(mStorageLowIntent);
289 }
290
291 /**
292 * Cancels low storage notification and sends OK intent.
293 */
294 private final void cancelNotification() {
295 if(localLOGV) Log.i(TAG, "Canceling low memory notification");
296 NotificationManager mNotificationMgr =
297 (NotificationManager)mContext.getSystemService(
298 Context.NOTIFICATION_SERVICE);
299 //cancel notification since memory has been freed
300 mNotificationMgr.cancel(LOW_MEMORY_NOTIFICATION_ID);
301
302 mContext.removeStickyBroadcast(mStorageLowIntent);
303 mContext.sendBroadcast(mStorageOkIntent);
304 }
305
306 public void updateMemory() {
307 int callingUid = getCallingUid();
308 if(callingUid != Process.SYSTEM_UID) {
309 return;
310 }
311 // force an early check
312 postCheckMemoryMsg(true, 0);
313 }
314}