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 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 {
    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 
    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 
    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         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      */
    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     */
    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     */
    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      */
    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      */
    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      */
    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 
    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