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 long mFreeMem;  // on /data
     73     private long mLastReportedFreeMem;
     74     private long mLastReportedFreeMemTime;
     75     private boolean mLowMemFlag=false;
     76     private Context mContext;
     77     private ContentResolver mContentResolver;
     78     private long mTotalMemory;  // on /data
     79     private StatFs mDataFileStats;
     80     private StatFs mSystemFileStats;
     81     private StatFs mCacheFileStats;
     82     private static final String DATA_PATH = "/data";
     83     private static final String SYSTEM_PATH = "/system";
     84     private static final String CACHE_PATH = "/cache";
     85     private long mThreadStartTime = -1;
     86     private boolean mClearSucceeded = false;
     87     private boolean mClearingCache;
     88     private Intent mStorageLowIntent;
     89     private Intent mStorageOkIntent;
     90     private CachePackageDataObserver mClearCacheObserver;
     91     private static final int _TRUE = 1;
     92     private static final int _FALSE = 0;
     93 
     94     /**
     95      * This string is used for ServiceManager access to this class.
     96      */
     97     static final String SERVICE = "devicestoragemonitor";
     98 
     99     /**
    100     * Handler that checks the amount of disk space on the device and sends a
    101     * notification if the device runs low on disk space
    102     */
    103     Handler mHandler = new Handler() {
    104         @Override
    105         public void handleMessage(Message msg) {
    106             //dont handle an invalid message
    107             if (msg.what != DEVICE_MEMORY_WHAT) {
    108                 Slog.e(TAG, "Will not process invalid message");
    109                 return;
    110             }
    111             checkMemory(msg.arg1 == _TRUE);
    112         }
    113     };
    114 
    115     class CachePackageDataObserver extends IPackageDataObserver.Stub {
    116         public void onRemoveCompleted(String packageName, boolean succeeded) {
    117             mClearSucceeded = succeeded;
    118             mClearingCache = false;
    119             if(localLOGV) Slog.i(TAG, " Clear succeeded:"+mClearSucceeded
    120                     +", mClearingCache:"+mClearingCache+" Forcing memory check");
    121             postCheckMemoryMsg(false, 0);
    122         }
    123     }
    124 
    125     private final void restatDataDir() {
    126         try {
    127             mDataFileStats.restat(DATA_PATH);
    128             mFreeMem = (long) mDataFileStats.getAvailableBlocks() *
    129                 mDataFileStats.getBlockSize();
    130         } catch (IllegalArgumentException e) {
    131             // use the old value of mFreeMem
    132         }
    133         // Allow freemem to be overridden by debug.freemem for testing
    134         String debugFreeMem = SystemProperties.get("debug.freemem");
    135         if (!"".equals(debugFreeMem)) {
    136             mFreeMem = Long.parseLong(debugFreeMem);
    137         }
    138         // Read the log interval from secure settings
    139         long freeMemLogInterval = Settings.Secure.getLong(mContentResolver,
    140                 Settings.Secure.SYS_FREE_STORAGE_LOG_INTERVAL,
    141                 DEFAULT_FREE_STORAGE_LOG_INTERVAL_IN_MINUTES)*60*1000;
    142         //log the amount of free memory in event log
    143         long currTime = SystemClock.elapsedRealtime();
    144         if((mLastReportedFreeMemTime == 0) ||
    145            (currTime-mLastReportedFreeMemTime) >= freeMemLogInterval) {
    146             mLastReportedFreeMemTime = currTime;
    147             long mFreeSystem = -1, mFreeCache = -1;
    148             try {
    149                 mSystemFileStats.restat(SYSTEM_PATH);
    150                 mFreeSystem = (long) mSystemFileStats.getAvailableBlocks() *
    151                     mSystemFileStats.getBlockSize();
    152             } catch (IllegalArgumentException e) {
    153                 // ignore; report -1
    154             }
    155             try {
    156                 mCacheFileStats.restat(CACHE_PATH);
    157                 mFreeCache = (long) mCacheFileStats.getAvailableBlocks() *
    158                     mCacheFileStats.getBlockSize();
    159             } catch (IllegalArgumentException e) {
    160                 // ignore; report -1
    161             }
    162             mCacheFileStats.restat(CACHE_PATH);
    163             EventLog.writeEvent(EventLogTags.FREE_STORAGE_LEFT,
    164                                 mFreeMem, mFreeSystem, mFreeCache);
    165         }
    166         // Read the reporting threshold from secure settings
    167         long threshold = Settings.Secure.getLong(mContentResolver,
    168                 Settings.Secure.DISK_FREE_CHANGE_REPORTING_THRESHOLD,
    169                 DEFAULT_DISK_FREE_CHANGE_REPORTING_THRESHOLD);
    170         // If mFree changed significantly log the new value
    171         long delta = mFreeMem - mLastReportedFreeMem;
    172         if (delta > threshold || delta < -threshold) {
    173             mLastReportedFreeMem = mFreeMem;
    174             EventLog.writeEvent(EventLogTags.FREE_STORAGE_CHANGED, mFreeMem);
    175         }
    176     }
    177 
    178     private final void clearCache() {
    179         if (mClearCacheObserver == null) {
    180             // Lazy instantiation
    181             mClearCacheObserver = new CachePackageDataObserver();
    182         }
    183         mClearingCache = true;
    184         try {
    185             if (localLOGV) Slog.i(TAG, "Clearing cache");
    186             IPackageManager.Stub.asInterface(ServiceManager.getService("package")).
    187                     freeStorageAndNotify(getMemThreshold(), mClearCacheObserver);
    188         } catch (RemoteException e) {
    189             Slog.w(TAG, "Failed to get handle for PackageManger Exception: "+e);
    190             mClearingCache = false;
    191             mClearSucceeded = false;
    192         }
    193     }
    194 
    195     private final void checkMemory(boolean checkCache) {
    196         //if the thread that was started to clear cache is still running do nothing till its
    197         //finished clearing cache. Ideally this flag could be modified by clearCache
    198         // and should be accessed via a lock but even if it does this test will fail now and
    199         //hopefully the next time this flag will be set to the correct value.
    200         if(mClearingCache) {
    201             if(localLOGV) Slog.i(TAG, "Thread already running just skip");
    202             //make sure the thread is not hung for too long
    203             long diffTime = System.currentTimeMillis() - mThreadStartTime;
    204             if(diffTime > (10*60*1000)) {
    205                 Slog.w(TAG, "Thread that clears cache file seems to run for ever");
    206             }
    207         } else {
    208             restatDataDir();
    209             if (localLOGV)  Slog.v(TAG, "freeMemory="+mFreeMem);
    210 
    211             //post intent to NotificationManager to display icon if necessary
    212             long memThreshold = getMemThreshold();
    213             if (mFreeMem < memThreshold) {
    214                 if (!mLowMemFlag) {
    215                     if (checkCache) {
    216                         // See if clearing cache helps
    217                         // Note that clearing cache is asynchronous and so we do a
    218                         // memory check again once the cache has been cleared.
    219                         mThreadStartTime = System.currentTimeMillis();
    220                         mClearSucceeded = false;
    221                         clearCache();
    222                     } else {
    223                         Slog.i(TAG, "Running low on memory. Sending notification");
    224                         sendNotification();
    225                         mLowMemFlag = true;
    226                     }
    227                 } else {
    228                     if (localLOGV) Slog.v(TAG, "Running low on memory " +
    229                             "notification already sent. do nothing");
    230                 }
    231             } else {
    232                 if (mLowMemFlag) {
    233                     Slog.i(TAG, "Memory available. Cancelling notification");
    234                     cancelNotification();
    235                     mLowMemFlag = false;
    236                 }
    237             }
    238         }
    239         if(localLOGV) Slog.i(TAG, "Posting Message again");
    240         //keep posting messages to itself periodically
    241         postCheckMemoryMsg(true, DEFAULT_CHECK_INTERVAL);
    242     }
    243 
    244     private void postCheckMemoryMsg(boolean clearCache, long delay) {
    245         // Remove queued messages
    246         mHandler.removeMessages(DEVICE_MEMORY_WHAT);
    247         mHandler.sendMessageDelayed(mHandler.obtainMessage(DEVICE_MEMORY_WHAT,
    248                 clearCache ?_TRUE : _FALSE, 0),
    249                 delay);
    250     }
    251 
    252     /*
    253      * just query settings to retrieve the memory threshold.
    254      * Preferred this over using a ContentObserver since Settings.Secure caches the value
    255      * any way
    256      */
    257     private long getMemThreshold() {
    258         int value = Settings.Secure.getInt(
    259                               mContentResolver,
    260                               Settings.Secure.SYS_STORAGE_THRESHOLD_PERCENTAGE,
    261                               DEFAULT_THRESHOLD_PERCENTAGE);
    262         if(localLOGV) Slog.v(TAG, "Threshold Percentage="+value);
    263         //evaluate threshold value
    264         return mTotalMemory*value;
    265     }
    266 
    267     /**
    268     * Constructor to run service. initializes the disk space threshold value
    269     * and posts an empty message to kickstart the process.
    270     */
    271     public DeviceStorageMonitorService(Context context) {
    272         mLastReportedFreeMemTime = 0;
    273         mContext = context;
    274         mContentResolver = mContext.getContentResolver();
    275         //create StatFs object
    276         mDataFileStats = new StatFs(DATA_PATH);
    277         mSystemFileStats = new StatFs(SYSTEM_PATH);
    278         mCacheFileStats = new StatFs(CACHE_PATH);
    279         //initialize total storage on device
    280         mTotalMemory = ((long)mDataFileStats.getBlockCount() *
    281                         mDataFileStats.getBlockSize())/100L;
    282         mStorageLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW);
    283         mStorageLowIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    284         mStorageOkIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK);
    285         mStorageOkIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    286         checkMemory(true);
    287     }
    288 
    289 
    290     /**
    291     * This method sends a notification to NotificationManager to display
    292     * an error dialog indicating low disk space and launch the Installer
    293     * application
    294     */
    295     private final void sendNotification() {
    296         if(localLOGV) Slog.i(TAG, "Sending low memory notification");
    297         //log the event to event log with the amount of free storage(in bytes) left on the device
    298         EventLog.writeEvent(EventLogTags.LOW_STORAGE, mFreeMem);
    299         //  Pack up the values and broadcast them to everyone
    300         Intent lowMemIntent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE);
    301         lowMemIntent.putExtra("memory", mFreeMem);
    302         lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    303         NotificationManager mNotificationMgr =
    304                 (NotificationManager)mContext.getSystemService(
    305                         Context.NOTIFICATION_SERVICE);
    306         CharSequence title = mContext.getText(
    307                 com.android.internal.R.string.low_internal_storage_view_title);
    308         CharSequence details = mContext.getText(
    309                 com.android.internal.R.string.low_internal_storage_view_text);
    310         PendingIntent intent = PendingIntent.getActivity(mContext, 0,  lowMemIntent, 0);
    311         Notification notification = new Notification();
    312         notification.icon = com.android.internal.R.drawable.stat_notify_disk_full;
    313         notification.tickerText = title;
    314         notification.flags |= Notification.FLAG_NO_CLEAR;
    315         notification.setLatestEventInfo(mContext, title, details, intent);
    316         mNotificationMgr.notify(LOW_MEMORY_NOTIFICATION_ID, notification);
    317         mContext.sendStickyBroadcast(mStorageLowIntent);
    318     }
    319 
    320     /**
    321      * Cancels low storage notification and sends OK intent.
    322      */
    323     private final void cancelNotification() {
    324         if(localLOGV) Slog.i(TAG, "Canceling low memory notification");
    325         NotificationManager mNotificationMgr =
    326                 (NotificationManager)mContext.getSystemService(
    327                         Context.NOTIFICATION_SERVICE);
    328         //cancel notification since memory has been freed
    329         mNotificationMgr.cancel(LOW_MEMORY_NOTIFICATION_ID);
    330 
    331         mContext.removeStickyBroadcast(mStorageLowIntent);
    332         mContext.sendBroadcast(mStorageOkIntent);
    333     }
    334 
    335     public void updateMemory() {
    336         int callingUid = getCallingUid();
    337         if(callingUid != Process.SYSTEM_UID) {
    338             return;
    339         }
    340         // force an early check
    341         postCheckMemoryMsg(true, 0);
    342     }
    343 }
    344