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