Home | History | Annotate | Download | only in server
      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 android.app.Notification;
     20 import android.app.NotificationManager;
     21 import android.app.PendingIntent;
     22 import android.content.ContentResolver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.pm.IPackageDataObserver;
     26 import android.content.pm.IPackageManager;
     27 import android.os.Binder;
     28 import android.os.Environment;
     29 import android.os.FileObserver;
     30 import android.os.Handler;
     31 import android.os.Message;
     32 import android.os.Process;
     33 import android.os.RemoteException;
     34 import android.os.ServiceManager;
     35 import android.os.StatFs;
     36 import android.os.SystemClock;
     37 import android.os.SystemProperties;
     38 import android.provider.Settings;
     39 import android.util.EventLog;
     40 import android.util.Slog;
     41 
     42 /**
     43  * This class implements a service to monitor the amount of disk
     44  * storage space on the device.  If the free storage on device is less
     45  * than a tunable threshold value (a secure settings parameter;
     46  * default 10%) a low memory notification is displayed to alert the
     47  * user. If the user clicks on the low memory notification the
     48  * Application Manager application gets launched to let the user free
     49  * storage space.
     50  *
     51  * Event log events: A low memory event with the free storage on
     52  * device in bytes is logged to the event log when the device goes low
     53  * on storage space.  The amount of free storage on the device is
     54  * periodically logged to the event log. The log interval is a secure
     55  * settings parameter with a default value of 12 hours.  When the free
     56  * storage differential goes below a threshold (again a secure
     57  * settings parameter with a default value of 2MB), the free memory is
     58  * logged to the event log.
     59  */
     60 public class DeviceStorageMonitorService extends Binder {
     61     private static final String TAG = "DeviceStorageMonitorService";
     62     private static final boolean DEBUG = false;
     63     private static final boolean localLOGV = false;
     64     private static final int DEVICE_MEMORY_WHAT = 1;
     65     private static final int MONITOR_INTERVAL = 1; //in minutes
     66     private static final int LOW_MEMORY_NOTIFICATION_ID = 1;
     67     private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
     68     private static final int DEFAULT_THRESHOLD_MAX_BYTES = 500*1024*1024; // 500MB
     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 final CacheFileDeletedObserver mCacheFileDeletedObserver;
     96     private static final int _TRUE = 1;
     97     private static final int _FALSE = 0;
     98     private long mMemLowThreshold;
     99     private int mMemFullThreshold;
    100 
    101     /**
    102      * This string is used for ServiceManager access to this class.
    103      */
    104     public static final String SERVICE = "devicestoragemonitor";
    105 
    106     /**
    107     * Handler that checks the amount of disk space on the device and sends a
    108     * notification if the device runs low on disk space
    109     */
    110     Handler mHandler = new Handler() {
    111         @Override
    112         public void handleMessage(Message msg) {
    113             //don't handle an invalid message
    114             if (msg.what != DEVICE_MEMORY_WHAT) {
    115                 Slog.e(TAG, "Will not process invalid message");
    116                 return;
    117             }
    118             checkMemory(msg.arg1 == _TRUE);
    119         }
    120     };
    121 
    122     class CachePackageDataObserver extends IPackageDataObserver.Stub {
    123         public void onRemoveCompleted(String packageName, boolean succeeded) {
    124             mClearSucceeded = succeeded;
    125             mClearingCache = false;
    126             if(localLOGV) Slog.i(TAG, " Clear succeeded:"+mClearSucceeded
    127                     +", mClearingCache:"+mClearingCache+" Forcing memory check");
    128             postCheckMemoryMsg(false, 0);
    129         }
    130     }
    131 
    132     private final void restatDataDir() {
    133         try {
    134             mDataFileStats.restat(DATA_PATH);
    135             mFreeMem = (long) mDataFileStats.getAvailableBlocks() *
    136                 mDataFileStats.getBlockSize();
    137         } catch (IllegalArgumentException e) {
    138             // use the old value of mFreeMem
    139         }
    140         // Allow freemem to be overridden by debug.freemem for testing
    141         String debugFreeMem = SystemProperties.get("debug.freemem");
    142         if (!"".equals(debugFreeMem)) {
    143             mFreeMem = Long.parseLong(debugFreeMem);
    144         }
    145         // Read the log interval from secure settings
    146         long freeMemLogInterval = Settings.Secure.getLong(mContentResolver,
    147                 Settings.Secure.SYS_FREE_STORAGE_LOG_INTERVAL,
    148                 DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES)*60*1000;
    149         //log the amount of free memory in event log
    150         long currTime = SystemClock.elapsedRealtime();
    151         if((mLastReportedFreeMemTime == 0) ||
    152            (currTime-mLastReportedFreeMemTime) >= freeMemLogInterval) {
    153             mLastReportedFreeMemTime = currTime;
    154             long mFreeSystem = -1, mFreeCache = -1;
    155             try {
    156                 mSystemFileStats.restat(SYSTEM_PATH);
    157                 mFreeSystem = (long) mSystemFileStats.getAvailableBlocks() *
    158                     mSystemFileStats.getBlockSize();
    159             } catch (IllegalArgumentException e) {
    160                 // ignore; report -1
    161             }
    162             try {
    163                 mCacheFileStats.restat(CACHE_PATH);
    164                 mFreeCache = (long) mCacheFileStats.getAvailableBlocks() *
    165                     mCacheFileStats.getBlockSize();
    166             } catch (IllegalArgumentException e) {
    167                 // ignore; report -1
    168             }
    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 
    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 
    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 
    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      */
    273     private long getMemThreshold() {
    274         long 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         value *= mTotalMemory;
    280         long maxValue = Settings.Secure.getInt(
    281                 mContentResolver,
    282                 Settings.Secure.SYS_STORAGE_THRESHOLD_MAX_BYTES,
    283                 DEFAULT_THRESHOLD_MAX_BYTES);
    284         //evaluate threshold value
    285         return value < maxValue ? value : maxValue;
    286     }
    287 
    288     /*
    289      * just query settings to retrieve the memory full threshold.
    290      * Preferred this over using a ContentObserver since Settings.Secure caches the value
    291      * any way
    292      */
    293     private int getMemFullThreshold() {
    294         int value = Settings.Secure.getInt(
    295                               mContentResolver,
    296                               Settings.Secure.SYS_STORAGE_FULL_THRESHOLD_BYTES,
    297                               DEFAULT_FULL_THRESHOLD_BYTES);
    298         if(localLOGV) Slog.v(TAG, "Full Threshold Bytes="+value);
    299         return value;
    300     }
    301 
    302     /**
    303     * Constructor to run service. initializes the disk space threshold value
    304     * and posts an empty message to kickstart the process.
    305     */
    306     public DeviceStorageMonitorService(Context context) {
    307         mLastReportedFreeMemTime = 0;
    308         mContext = context;
    309         mContentResolver = mContext.getContentResolver();
    310         //create StatFs object
    311         mDataFileStats = new StatFs(DATA_PATH);
    312         mSystemFileStats = new StatFs(SYSTEM_PATH);
    313         mCacheFileStats = new StatFs(CACHE_PATH);
    314         //initialize total storage on device
    315         mTotalMemory = ((long)mDataFileStats.getBlockCount() *
    316                         mDataFileStats.getBlockSize())/100L;
    317         mStorageLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW);
    318         mStorageLowIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    319         mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK);
    320         mStorageOkIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    321         mStorageFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL);
    322         mStorageFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    323         mStorageNotFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL);
    324         mStorageNotFullIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    325         // cache storage thresholds
    326         mMemLowThreshold = getMemThreshold();
    327         mMemFullThreshold = getMemFullThreshold();
    328         checkMemory(true);
    329 
    330         mCacheFileDeletedObserver = new CacheFileDeletedObserver();
    331         mCacheFileDeletedObserver.startWatching();
    332     }
    333 
    334 
    335     /**
    336     * This method sends a notification to NotificationManager to display
    337     * an error dialog indicating low disk space and launch the Installer
    338     * application
    339     */
    340     private final void sendNotification() {
    341         if(localLOGV) Slog.i(TAG, "Sending low memory notification");
    342         //log the event to event log with the amount of free storage(in bytes) left on the device
    343         EventLog.writeEvent(EventLogTags.LOW_STORAGE, mFreeMem);
    344         //  Pack up the values and broadcast them to everyone
    345         Intent lowMemIntent = new Intent(Environment.isExternalStorageEmulated()
    346                 ? Settings.ACTION_INTERNAL_STORAGE_SETTINGS
    347                 : Intent.ACTION_MANAGE_PACKAGE_STORAGE);
    348         lowMemIntent.putExtra("memory", mFreeMem);
    349         lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    350         NotificationManager mNotificationMgr =
    351                 (NotificationManager)mContext.getSystemService(
    352                         Context.NOTIFICATION_SERVICE);
    353         CharSequence title = mContext.getText(
    354                 com.android.internal.R.string.low_internal_storage_view_title);
    355         CharSequence details = mContext.getText(
    356                 com.android.internal.R.string.low_internal_storage_view_text);
    357         PendingIntent intent = PendingIntent.getActivity(mContext, 0,  lowMemIntent, 0);
    358         Notification notification = new Notification();
    359         notification.icon = com.android.internal.R.drawable.stat_notify_disk_full;
    360         notification.tickerText = title;
    361         notification.flags |= Notification.FLAG_NO_CLEAR;
    362         notification.setLatestEventInfo(mContext, title, details, intent);
    363         mNotificationMgr.notify(LOW_MEMORY_NOTIFICATION_ID, notification);
    364         mContext.sendStickyBroadcast(mStorageLowIntent);
    365     }
    366 
    367     /**
    368      * Cancels low storage notification and sends OK intent.
    369      */
    370     private final void cancelNotification() {
    371         if(localLOGV) Slog.i(TAG, "Canceling low memory notification");
    372         NotificationManager mNotificationMgr =
    373                 (NotificationManager)mContext.getSystemService(
    374                         Context.NOTIFICATION_SERVICE);
    375         //cancel notification since memory has been freed
    376         mNotificationMgr.cancel(LOW_MEMORY_NOTIFICATION_ID);
    377 
    378         mContext.removeStickyBroadcast(mStorageLowIntent);
    379         mContext.sendBroadcast(mStorageOkIntent);
    380     }
    381 
    382     /**
    383      * Send a notification when storage is full.
    384      */
    385     private final void sendFullNotification() {
    386         if(localLOGV) Slog.i(TAG, "Sending memory full notification");
    387         mContext.sendStickyBroadcast(mStorageFullIntent);
    388     }
    389 
    390     /**
    391      * Cancels memory full notification and sends "not full" intent.
    392      */
    393     private final void cancelFullNotification() {
    394         if(localLOGV) Slog.i(TAG, "Canceling memory full notification");
    395         mContext.removeStickyBroadcast(mStorageFullIntent);
    396         mContext.sendBroadcast(mStorageNotFullIntent);
    397     }
    398 
    399     public void updateMemory() {
    400         int callingUid = getCallingUid();
    401         if(callingUid != Process.SYSTEM_UID) {
    402             return;
    403         }
    404         // force an early check
    405         postCheckMemoryMsg(true, 0);
    406     }
    407 
    408     /**
    409      * Callable from other things in the system service to obtain the low memory
    410      * threshold.
    411      *
    412      * @return low memory threshold in bytes
    413      */
    414     public long getMemoryLowThreshold() {
    415         return mMemLowThreshold;
    416     }
    417 
    418     /**
    419      * Callable from other things in the system process to check whether memory
    420      * is low.
    421      *
    422      * @return true is memory is low
    423      */
    424     public boolean isMemoryLow() {
    425         return mLowMemFlag;
    426     }
    427 
    428     public static class CacheFileDeletedObserver extends FileObserver {
    429         public CacheFileDeletedObserver() {
    430             super(Environment.getDownloadCacheDirectory().getAbsolutePath(), FileObserver.DELETE);
    431         }
    432 
    433         @Override
    434         public void onEvent(int event, String path) {
    435             EventLogTags.writeCacheFileDeleted(path);
    436         }
    437     }
    438 }
    439