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