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
17package com.android.server.storage;
18
19import com.android.server.EventLogTags;
20import com.android.server.SystemService;
21import com.android.server.pm.InstructionSets;
22import android.app.Notification;
23import android.app.NotificationManager;
24import android.app.PendingIntent;
25import android.content.ContentResolver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.pm.IPackageDataObserver;
29import android.content.pm.IPackageManager;
30import android.content.pm.PackageManager;
31import android.os.Binder;
32import android.os.Environment;
33import android.os.FileObserver;
34import android.os.Handler;
35import android.os.IBinder;
36import android.os.Message;
37import android.os.RemoteException;
38import android.os.ServiceManager;
39import android.os.StatFs;
40import android.os.SystemClock;
41import android.os.SystemProperties;
42import android.os.UserHandle;
43import android.os.storage.StorageManager;
44import android.provider.Settings;
45import android.text.format.Formatter;
46import android.util.EventLog;
47import android.util.Slog;
48import android.util.TimeUtils;
49
50import java.io.File;
51import java.io.FileDescriptor;
52import java.io.PrintWriter;
53
54import dalvik.system.VMRuntime;
55
56/**
57 * This class implements a service to monitor the amount of disk
58 * storage space on the device.  If the free storage on device is less
59 * than a tunable threshold value (a secure settings parameter;
60 * default 10%) a low memory notification is displayed to alert the
61 * user. If the user clicks on the low memory notification the
62 * Application Manager application gets launched to let the user free
63 * storage space.
64 *
65 * Event log events: A low memory event with the free storage on
66 * device in bytes is logged to the event log when the device goes low
67 * on storage space.  The amount of free storage on the device is
68 * periodically logged to the event log. The log interval is a secure
69 * settings parameter with a default value of 12 hours.  When the free
70 * storage differential goes below a threshold (again a secure
71 * settings parameter with a default value of 2MB), the free memory is
72 * logged to the event log.
73 */
74public class DeviceStorageMonitorService extends SystemService {
75    static final String TAG = "DeviceStorageMonitorService";
76
77    // TODO: extend to watch and manage caches on all private volumes
78
79    static final boolean DEBUG = false;
80    static final boolean localLOGV = false;
81
82    static final int DEVICE_MEMORY_WHAT = 1;
83    private static final int MONITOR_INTERVAL = 1; //in minutes
84    private static final int LOW_MEMORY_NOTIFICATION_ID = 1;
85
86    private static final int DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES = 12*60; //in minutes
87    private static final long DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD = 2 * 1024 * 1024; // 2MB
88    private static final long DEFAULT_CHECK_INTERVAL = MONITOR_INTERVAL*60*1000;
89
90    private long mFreeMem;  // on /data
91    private long mFreeMemAfterLastCacheClear;  // on /data
92    private long mLastReportedFreeMem;
93    private long mLastReportedFreeMemTime;
94    boolean mLowMemFlag=false;
95    private boolean mMemFullFlag=false;
96    private final boolean mIsBootImageOnDisk;
97    private final ContentResolver mResolver;
98    private final long mTotalMemory;  // on /data
99    private final StatFs mDataFileStats;
100    private final StatFs mSystemFileStats;
101    private final StatFs mCacheFileStats;
102
103    private static final File DATA_PATH = Environment.getDataDirectory();
104    private static final File SYSTEM_PATH = Environment.getRootDirectory();
105    private static final File CACHE_PATH = Environment.getDownloadCacheDirectory();
106
107    private long mThreadStartTime = -1;
108    boolean mClearSucceeded = false;
109    boolean mClearingCache;
110    private final Intent mStorageLowIntent;
111    private final Intent mStorageOkIntent;
112    private final Intent mStorageFullIntent;
113    private final Intent mStorageNotFullIntent;
114    private CachePackageDataObserver mClearCacheObserver;
115    private CacheFileDeletedObserver mCacheFileDeletedObserver;
116    private static final int _TRUE = 1;
117    private static final int _FALSE = 0;
118    // This is the raw threshold that has been set at which we consider
119    // storage to be low.
120    long mMemLowThreshold;
121    // This is the threshold at which we start trying to flush caches
122    // to get below the low threshold limit.  It is less than the low
123    // threshold; we will allow storage to get a bit beyond the limit
124    // before flushing and checking if we are actually low.
125    private long mMemCacheStartTrimThreshold;
126    // This is the threshold that we try to get to when deleting cache
127    // files.  This is greater than the low threshold so that we will flush
128    // more files than absolutely needed, to reduce the frequency that
129    // flushing takes place.
130    private long mMemCacheTrimToThreshold;
131    private long mMemFullThreshold;
132
133    /**
134     * This string is used for ServiceManager access to this class.
135     */
136    static final String SERVICE = "devicestoragemonitor";
137
138    /**
139    * Handler that checks the amount of disk space on the device and sends a
140    * notification if the device runs low on disk space
141    */
142    private final Handler mHandler = new Handler() {
143        @Override
144        public void handleMessage(Message msg) {
145            //don't handle an invalid message
146            if (msg.what != DEVICE_MEMORY_WHAT) {
147                Slog.e(TAG, "Will not process invalid message");
148                return;
149            }
150            checkMemory(msg.arg1 == _TRUE);
151        }
152    };
153
154    private class CachePackageDataObserver extends IPackageDataObserver.Stub {
155        public void onRemoveCompleted(String packageName, boolean succeeded) {
156            mClearSucceeded = succeeded;
157            mClearingCache = false;
158            if(localLOGV) Slog.i(TAG, " Clear succeeded:"+mClearSucceeded
159                    +", mClearingCache:"+mClearingCache+" Forcing memory check");
160            postCheckMemoryMsg(false, 0);
161        }
162    }
163
164    private void restatDataDir() {
165        try {
166            mDataFileStats.restat(DATA_PATH.getAbsolutePath());
167            mFreeMem = (long) mDataFileStats.getAvailableBlocks() *
168                mDataFileStats.getBlockSize();
169        } catch (IllegalArgumentException e) {
170            // use the old value of mFreeMem
171        }
172        // Allow freemem to be overridden by debug.freemem for testing
173        String debugFreeMem = SystemProperties.get("debug.freemem");
174        if (!"".equals(debugFreeMem)) {
175            mFreeMem = Long.parseLong(debugFreeMem);
176        }
177        // Read the log interval from secure settings
178        long freeMemLogInterval = Settings.Global.getLong(mResolver,
179                Settings.Global.SYS_FREE_STORAGE_LOG_INTERVAL,
180                DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES)*60*1000;
181        //log the amount of free memory in event log
182        long currTime = SystemClock.elapsedRealtime();
183        if((mLastReportedFreeMemTime == 0) ||
184           (currTime-mLastReportedFreeMemTime) >= freeMemLogInterval) {
185            mLastReportedFreeMemTime = currTime;
186            long mFreeSystem = -1, mFreeCache = -1;
187            try {
188                mSystemFileStats.restat(SYSTEM_PATH.getAbsolutePath());
189                mFreeSystem = (long) mSystemFileStats.getAvailableBlocks() *
190                    mSystemFileStats.getBlockSize();
191            } catch (IllegalArgumentException e) {
192                // ignore; report -1
193            }
194            try {
195                mCacheFileStats.restat(CACHE_PATH.getAbsolutePath());
196                mFreeCache = (long) mCacheFileStats.getAvailableBlocks() *
197                    mCacheFileStats.getBlockSize();
198            } catch (IllegalArgumentException e) {
199                // ignore; report -1
200            }
201            EventLog.writeEvent(EventLogTags.FREE_STORAGE_LEFT,
202                                mFreeMem, mFreeSystem, mFreeCache);
203        }
204        // Read the reporting threshold from secure settings
205        long threshold = Settings.Global.getLong(mResolver,
206                Settings.Global.DISK_FREE_CHANGE_REPORTING_THRESHOLD,
207                DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD);
208        // If mFree changed significantly log the new value
209        long delta = mFreeMem - mLastReportedFreeMem;
210        if (delta > threshold || delta < -threshold) {
211            mLastReportedFreeMem = mFreeMem;
212            EventLog.writeEvent(EventLogTags.FREE_STORAGE_CHANGED, mFreeMem);
213        }
214    }
215
216    private void clearCache() {
217        if (mClearCacheObserver == null) {
218            // Lazy instantiation
219            mClearCacheObserver = new CachePackageDataObserver();
220        }
221        mClearingCache = true;
222        try {
223            if (localLOGV) Slog.i(TAG, "Clearing cache");
224            IPackageManager.Stub.asInterface(ServiceManager.getService("package")).
225                    freeStorageAndNotify(null, mMemCacheTrimToThreshold, mClearCacheObserver);
226        } catch (RemoteException e) {
227            Slog.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
228            mClearingCache = false;
229            mClearSucceeded = false;
230        }
231    }
232
233    void checkMemory(boolean checkCache) {
234        //if the thread that was started to clear cache is still running do nothing till its
235        //finished clearing cache. Ideally this flag could be modified by clearCache
236        // and should be accessed via a lock but even if it does this test will fail now and
237        //hopefully the next time this flag will be set to the correct value.
238        if(mClearingCache) {
239            if(localLOGV) Slog.i(TAG, "Thread already running just skip");
240            //make sure the thread is not hung for too long
241            long diffTime = System.currentTimeMillis() - mThreadStartTime;
242            if(diffTime > (10*60*1000)) {
243                Slog.w(TAG, "Thread that clears cache file seems to run for ever");
244            }
245        } else {
246            restatDataDir();
247            if (localLOGV)  Slog.v(TAG, "freeMemory="+mFreeMem);
248
249            //post intent to NotificationManager to display icon if necessary
250            if (mFreeMem < mMemLowThreshold) {
251                if (checkCache) {
252                    // We are allowed to clear cache files at this point to
253                    // try to get down below the limit, because this is not
254                    // the initial call after a cache clear has been attempted.
255                    // In this case we will try a cache clear if our free
256                    // space has gone below the cache clear limit.
257                    if (mFreeMem < mMemCacheStartTrimThreshold) {
258                        // We only clear the cache if the free storage has changed
259                        // a significant amount since the last time.
260                        if ((mFreeMemAfterLastCacheClear-mFreeMem)
261                                >= ((mMemLowThreshold-mMemCacheStartTrimThreshold)/4)) {
262                            // See if clearing cache helps
263                            // Note that clearing cache is asynchronous and so we do a
264                            // memory check again once the cache has been cleared.
265                            mThreadStartTime = System.currentTimeMillis();
266                            mClearSucceeded = false;
267                            clearCache();
268                        }
269                    }
270                } else {
271                    // This is a call from after clearing the cache.  Note
272                    // the amount of free storage at this point.
273                    mFreeMemAfterLastCacheClear = mFreeMem;
274                    if (!mLowMemFlag) {
275                        // We tried to clear the cache, but that didn't get us
276                        // below the low storage limit.  Tell the user.
277                        Slog.i(TAG, "Running low on memory. Sending notification");
278                        sendNotification();
279                        mLowMemFlag = true;
280                    } else {
281                        if (localLOGV) Slog.v(TAG, "Running low on memory " +
282                                "notification already sent. do nothing");
283                    }
284                }
285            } else {
286                mFreeMemAfterLastCacheClear = mFreeMem;
287                if (mLowMemFlag) {
288                    Slog.i(TAG, "Memory available. Cancelling notification");
289                    cancelNotification();
290                    mLowMemFlag = false;
291                }
292            }
293            if (!mLowMemFlag && !mIsBootImageOnDisk) {
294                Slog.i(TAG, "No boot image on disk due to lack of space. Sending notification");
295                sendNotification();
296            }
297            if (mFreeMem < mMemFullThreshold) {
298                if (!mMemFullFlag) {
299                    sendFullNotification();
300                    mMemFullFlag = true;
301                }
302            } else {
303                if (mMemFullFlag) {
304                    cancelFullNotification();
305                    mMemFullFlag = false;
306                }
307            }
308        }
309        if(localLOGV) Slog.i(TAG, "Posting Message again");
310        //keep posting messages to itself periodically
311        postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL);
312    }
313
314    void postCheckMemoryMsg(boolean clearCache, long delay) {
315        // Remove queued messages
316        mHandler.removeMessages(DEVICE_MEMORY_WHAT);
317        mHandler.sendMessageDelayed(mHandler.obtainMessage(DEVICE_MEMORY_WHAT,
318                clearCache ?_TRUE : _FALSE, 0),
319                delay);
320    }
321
322    public DeviceStorageMonitorService(Context context) {
323        super(context);
324        mLastReportedFreeMemTime = 0;
325        mResolver = context.getContentResolver();
326        mIsBootImageOnDisk = isBootImageOnDisk();
327        //create StatFs object
328        mDataFileStats = new StatFs(DATA_PATH.getAbsolutePath());
329        mSystemFileStats = new StatFs(SYSTEM_PATH.getAbsolutePath());
330        mCacheFileStats = new StatFs(CACHE_PATH.getAbsolutePath());
331        //initialize total storage on device
332        mTotalMemory = (long)mDataFileStats.getBlockCount() *
333                        mDataFileStats.getBlockSize();
334        mStorageLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW);
335        mStorageLowIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
336        mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK);
337        mStorageOkIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
338        mStorageFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL);
339        mStorageFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
340        mStorageNotFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
341        mStorageNotFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
342    }
343
344    private static boolean isBootImageOnDisk() {
345        for (String instructionSet : InstructionSets.getAllDexCodeInstructionSets()) {
346            if (!VMRuntime.isBootClassPathOnDisk(instructionSet)) {
347                return false;
348            }
349        }
350        return true;
351    }
352
353    /**
354    * Initializes the disk space threshold value and posts an empty message to
355    * kickstart the process.
356    */
357    @Override
358    public void onStart() {
359        // cache storage thresholds
360        final StorageManager sm = StorageManager.from(getContext());
361        mMemLowThreshold = sm.getStorageLowBytes(DATA_PATH);
362        mMemFullThreshold = sm.getStorageFullBytes(DATA_PATH);
363
364        mMemCacheStartTrimThreshold = ((mMemLowThreshold*3)+mMemFullThreshold)/4;
365        mMemCacheTrimToThreshold = mMemLowThreshold
366                + ((mMemLowThreshold-mMemCacheStartTrimThreshold)*2);
367        mFreeMemAfterLastCacheClear = mTotalMemory;
368        checkMemory(true);
369
370        mCacheFileDeletedObserver = new CacheFileDeletedObserver();
371        mCacheFileDeletedObserver.startWatching();
372
373        publishBinderService(SERVICE, mRemoteService);
374        publishLocalService(DeviceStorageMonitorInternal.class, mLocalService);
375    }
376
377    private final DeviceStorageMonitorInternal mLocalService = new DeviceStorageMonitorInternal() {
378        @Override
379        public void checkMemory() {
380            // force an early check
381            postCheckMemoryMsg(true, 0);
382        }
383
384        @Override
385        public boolean isMemoryLow() {
386            return mLowMemFlag || !mIsBootImageOnDisk;
387        }
388
389        @Override
390        public long getMemoryLowThreshold() {
391            return mMemLowThreshold;
392        }
393    };
394
395    private final IBinder mRemoteService = new Binder() {
396        @Override
397        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
398            if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
399                    != PackageManager.PERMISSION_GRANTED) {
400
401                pw.println("Permission Denial: can't dump " + SERVICE + " from from pid="
402                        + Binder.getCallingPid()
403                        + ", uid=" + Binder.getCallingUid());
404                return;
405            }
406
407            dumpImpl(pw);
408        }
409    };
410
411    void dumpImpl(PrintWriter pw) {
412        final Context context = getContext();
413
414        pw.println("Current DeviceStorageMonitor state:");
415
416        pw.print("  mFreeMem="); pw.print(Formatter.formatFileSize(context, mFreeMem));
417        pw.print(" mTotalMemory=");
418        pw.println(Formatter.formatFileSize(context, mTotalMemory));
419
420        pw.print("  mFreeMemAfterLastCacheClear=");
421        pw.println(Formatter.formatFileSize(context, mFreeMemAfterLastCacheClear));
422
423        pw.print("  mLastReportedFreeMem=");
424        pw.print(Formatter.formatFileSize(context, mLastReportedFreeMem));
425        pw.print(" mLastReportedFreeMemTime=");
426        TimeUtils.formatDuration(mLastReportedFreeMemTime, SystemClock.elapsedRealtime(), pw);
427        pw.println();
428
429        pw.print("  mLowMemFlag="); pw.print(mLowMemFlag);
430        pw.print(" mMemFullFlag="); pw.println(mMemFullFlag);
431        pw.print(" mIsBootImageOnDisk="); pw.print(mIsBootImageOnDisk);
432
433        pw.print("  mClearSucceeded="); pw.print(mClearSucceeded);
434        pw.print(" mClearingCache="); pw.println(mClearingCache);
435
436        pw.print("  mMemLowThreshold=");
437        pw.print(Formatter.formatFileSize(context, mMemLowThreshold));
438        pw.print(" mMemFullThreshold=");
439        pw.println(Formatter.formatFileSize(context, mMemFullThreshold));
440
441        pw.print("  mMemCacheStartTrimThreshold=");
442        pw.print(Formatter.formatFileSize(context, mMemCacheStartTrimThreshold));
443        pw.print(" mMemCacheTrimToThreshold=");
444        pw.println(Formatter.formatFileSize(context, mMemCacheTrimToThreshold));
445    }
446
447    /**
448    * This method sends a notification to NotificationManager to display
449    * an error dialog indicating low disk space and launch the Installer
450    * application
451    */
452    private void sendNotification() {
453        final Context context = getContext();
454        if(localLOGV) Slog.i(TAG, "Sending low memory notification");
455        //log the event to event log with the amount of free storage(in bytes) left on the device
456        EventLog.writeEvent(EventLogTags.LOW_STORAGE, mFreeMem);
457        //  Pack up the values and broadcast them to everyone
458        Intent lowMemIntent = new Intent(Environment.isExternalStorageEmulated()
459                ? Settings.ACTION_INTERNAL_STORAGE_SETTINGS
460                : Intent.ACTION_MANAGE_PACKAGE_STORAGE);
461        lowMemIntent.putExtra("memory", mFreeMem);
462        lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
463        NotificationManager mNotificationMgr =
464                (NotificationManager)context.getSystemService(
465                        Context.NOTIFICATION_SERVICE);
466        CharSequence title = context.getText(
467                com.android.internal.R.string.low_internal_storage_view_title);
468        CharSequence details = context.getText(mIsBootImageOnDisk
469                ? com.android.internal.R.string.low_internal_storage_view_text
470                : com.android.internal.R.string.low_internal_storage_view_text_no_boot);
471        PendingIntent intent = PendingIntent.getActivityAsUser(context, 0,  lowMemIntent, 0,
472                null, UserHandle.CURRENT);
473        Notification notification = new Notification.Builder(context)
474                .setSmallIcon(com.android.internal.R.drawable.stat_notify_disk_full)
475                .setTicker(title)
476                .setColor(context.getColor(
477                    com.android.internal.R.color.system_notification_accent_color))
478                .setContentTitle(title)
479                .setContentText(details)
480                .setContentIntent(intent)
481                .setStyle(new Notification.BigTextStyle()
482                      .bigText(details))
483                .setVisibility(Notification.VISIBILITY_PUBLIC)
484                .setCategory(Notification.CATEGORY_SYSTEM)
485                .build();
486        notification.flags |= Notification.FLAG_NO_CLEAR;
487        mNotificationMgr.notifyAsUser(null, LOW_MEMORY_NOTIFICATION_ID, notification,
488                UserHandle.ALL);
489        context.sendStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
490    }
491
492    /**
493     * Cancels low storage notification and sends OK intent.
494     */
495    private void cancelNotification() {
496        final Context context = getContext();
497        if(localLOGV) Slog.i(TAG, "Canceling low memory notification");
498        NotificationManager mNotificationMgr =
499                (NotificationManager)context.getSystemService(
500                        Context.NOTIFICATION_SERVICE);
501        //cancel notification since memory has been freed
502        mNotificationMgr.cancelAsUser(null, LOW_MEMORY_NOTIFICATION_ID, UserHandle.ALL);
503
504        context.removeStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL);
505        context.sendBroadcastAsUser(mStorageOkIntent, UserHandle.ALL);
506    }
507
508    /**
509     * Send a notification when storage is full.
510     */
511    private void sendFullNotification() {
512        if(localLOGV) Slog.i(TAG, "Sending memory full notification");
513        getContext().sendStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL);
514    }
515
516    /**
517     * Cancels memory full notification and sends "not full" intent.
518     */
519    private void cancelFullNotification() {
520        if(localLOGV) Slog.i(TAG, "Canceling memory full notification");
521        getContext().removeStickyBroadcastAsUser(mStorageFullIntent, UserHandle.ALL);
522        getContext().sendBroadcastAsUser(mStorageNotFullIntent, UserHandle.ALL);
523    }
524
525    private static class CacheFileDeletedObserver extends FileObserver {
526        public CacheFileDeletedObserver() {
527            super(Environment.getDownloadCacheDirectory().getAbsolutePath(), FileObserver.DELETE);
528        }
529
530        @Override
531        public void onEvent(int event, String path) {
532            EventLogTags.writeCacheFileDeleted(path);
533        }
534    }
535}
536