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