• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 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 static final int _TRUE = 1;
96     private static final int _FALSE = 0;
97     private long mMemLowThreshold;
98     private int mMemFullThreshold;
99 
100     /**
101      * This string is used for ServiceManager access to this class.
102      */
103     static final String SERVICE = "devicestoragemonitor";
104 
105     /**
106     * Handler that checks the amount of disk space on the device and sends a
107     * notification if the device runs low on disk space
108     */
109     Handler mHandler = new Handler() {
110         @Override
111         public void handleMessage(Message msg) {
112             //don't handle an invalid message
113             if (msg.what != DEVICE_MEMORY_WHAT) {
114                 Slog.e(TAG, "Will not process invalid message");
115                 return;
116             }
117             checkMemory(msg.arg1 == _TRUE);
118         }
119     };
120 
121     class CachePackageDataObserver extends IPackageDataObserver.Stub {
onRemoveCompleted(String packageName, boolean succeeded)122         public void onRemoveCompleted(String packageName, boolean succeeded) {
123             mClearSucceeded = succeeded;
124             mClearingCache = false;
125             if(localLOGV) Slog.i(TAG, " Clear succeeded:"+mClearSucceeded
126                     +", mClearingCache:"+mClearingCache+" Forcing memory check");
127             postCheckMemoryMsg(false, 0);
128         }
129     }
130 
restatDataDir()131     private final void restatDataDir() {
132         try {
133             mDataFileStats.restat(DATA_PATH);
134             mFreeMem = (long) mDataFileStats.getAvailableBlocks() *
135                 mDataFileStats.getBlockSize();
136         } catch (IllegalArgumentException e) {
137             // use the old value of mFreeMem
138         }
139         // Allow freemem to be overridden by debug.freemem for testing
140         String debugFreeMem = SystemProperties.get("debug.freemem");
141         if (!"".equals(debugFreeMem)) {
142             mFreeMem = Long.parseLong(debugFreeMem);
143         }
144         // Read the log interval from secure settings
145         long freeMemLogInterval = Settings.Secure.getLong(mContentResolver,
146                 Settings.Secure.SYS_FREE_STORAGE_LOG_INTERVAL,
147                 DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES)*60*1000;
148         //log the amount of free memory in event log
149         long currTime = SystemClock.elapsedRealtime();
150         if((mLastReportedFreeMemTime == 0) ||
151            (currTime-mLastReportedFreeMemTime) >= freeMemLogInterval) {
152             mLastReportedFreeMemTime = currTime;
153             long mFreeSystem = -1, mFreeCache = -1;
154             try {
155                 mSystemFileStats.restat(SYSTEM_PATH);
156                 mFreeSystem = (long) mSystemFileStats.getAvailableBlocks() *
157                     mSystemFileStats.getBlockSize();
158             } catch (IllegalArgumentException e) {
159                 // ignore; report -1
160             }
161             try {
162                 mCacheFileStats.restat(CACHE_PATH);
163                 mFreeCache = (long) mCacheFileStats.getAvailableBlocks() *
164                     mCacheFileStats.getBlockSize();
165             } catch (IllegalArgumentException e) {
166                 // ignore; report -1
167             }
168             mCacheFileStats.restat(CACHE_PATH);
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 
clearCache()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 
checkMemory(boolean checkCache)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 
postCheckMemoryMsg(boolean clearCache, long delay)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      */
getMemThreshold()273     private long getMemThreshold() {
274         int 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         //evaluate threshold value
280         return mTotalMemory*value;
281     }
282 
283     /*
284      * just query settings to retrieve the memory full threshold.
285      * Preferred this over using a ContentObserver since Settings.Secure caches the value
286      * any way
287      */
getMemFullThreshold()288     private int getMemFullThreshold() {
289         int value = Settings.Secure.getInt(
290                               mContentResolver,
291                               Settings.Secure.SYS_STORAGE_FULL_THRESHOLD_BYTES,
292                               DEFAULT_FULL_THRESHOLD_BYTES);
293         if(localLOGV) Slog.v(TAG, "Full Threshold Bytes="+value);
294         return value;
295     }
296 
297     /**
298     * Constructor to run service. initializes the disk space threshold value
299     * and posts an empty message to kickstart the process.
300     */
DeviceStorageMonitorService(Context context)301     public DeviceStorageMonitorService(Context context) {
302         mLastReportedFreeMemTime = 0;
303         mContext = context;
304         mContentResolver = mContext.getContentResolver();
305         //create StatFs object
306         mDataFileStats = new StatFs(DATA_PATH);
307         mSystemFileStats = new StatFs(SYSTEM_PATH);
308         mCacheFileStats = new StatFs(CACHE_PATH);
309         //initialize total storage on device
310         mTotalMemory = ((long)mDataFileStats.getBlockCount() *
311                         mDataFileStats.getBlockSize())/100L;
312         mStorageLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW);
313         mStorageLowIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
314         mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK);
315         mStorageOkIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
316         mStorageFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL);
317         mStorageFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
318         mStorageNotFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
319         mStorageNotFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
320         // cache storage thresholds
321         mMemLowThreshold = getMemThreshold();
322         mMemFullThreshold = getMemFullThreshold();
323         checkMemory(true);
324     }
325 
326 
327     /**
328     * This method sends a notification to NotificationManager to display
329     * an error dialog indicating low disk space and launch the Installer
330     * application
331     */
sendNotification()332     private final void sendNotification() {
333         if(localLOGV) Slog.i(TAG, "Sending low memory notification");
334         //log the event to event log with the amount of free storage(in bytes) left on the device
335         EventLog.writeEvent(EventLogTags.LOW_STORAGE, mFreeMem);
336         //  Pack up the values and broadcast them to everyone
337         Intent lowMemIntent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE);
338         lowMemIntent.putExtra("memory", mFreeMem);
339         lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
340         NotificationManager mNotificationMgr =
341                 (NotificationManager)mContext.getSystemService(
342                         Context.NOTIFICATION_SERVICE);
343         CharSequence title = mContext.getText(
344                 com.android.internal.R.string.low_internal_storage_view_title);
345         CharSequence details = mContext.getText(
346                 com.android.internal.R.string.low_internal_storage_view_text);
347         PendingIntent intent = PendingIntent.getActivity(mContext, 0,  lowMemIntent, 0);
348         Notification notification = new Notification();
349         notification.icon = com.android.internal.R.drawable.stat_notify_disk_full;
350         notification.tickerText = title;
351         notification.flags |= Notification.FLAG_NO_CLEAR;
352         notification.setLatestEventInfo(mContext, title, details, intent);
353         mNotificationMgr.notify(LOW_MEMORY_NOTIFICATION_ID, notification);
354         mContext.sendStickyBroadcast(mStorageLowIntent);
355     }
356 
357     /**
358      * Cancels low storage notification and sends OK intent.
359      */
cancelNotification()360     private final void cancelNotification() {
361         if(localLOGV) Slog.i(TAG, "Canceling low memory notification");
362         NotificationManager mNotificationMgr =
363                 (NotificationManager)mContext.getSystemService(
364                         Context.NOTIFICATION_SERVICE);
365         //cancel notification since memory has been freed
366         mNotificationMgr.cancel(LOW_MEMORY_NOTIFICATION_ID);
367 
368         mContext.removeStickyBroadcast(mStorageLowIntent);
369         mContext.sendBroadcast(mStorageOkIntent);
370     }
371 
372     /**
373      * Send a notification when storage is full.
374      */
sendFullNotification()375     private final void sendFullNotification() {
376         if(localLOGV) Slog.i(TAG, "Sending memory full notification");
377         mContext.sendStickyBroadcast(mStorageFullIntent);
378     }
379 
380     /**
381      * Cancels memory full notification and sends "not full" intent.
382      */
cancelFullNotification()383     private final void cancelFullNotification() {
384         if(localLOGV) Slog.i(TAG, "Canceling memory full notification");
385         mContext.removeStickyBroadcast(mStorageFullIntent);
386         mContext.sendBroadcast(mStorageNotFullIntent);
387     }
388 
updateMemory()389     public void updateMemory() {
390         int callingUid = getCallingUid();
391         if(callingUid != Process.SYSTEM_UID) {
392             return;
393         }
394         // force an early check
395         postCheckMemoryMsg(true, 0);
396     }
397 }
398