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