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 android.annotation.WorkerThread;
     20 import android.app.Notification;
     21 import android.app.NotificationChannel;
     22 import android.app.NotificationManager;
     23 import android.app.PendingIntent;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.pm.PackageManager;
     27 import android.os.Binder;
     28 import android.os.Environment;
     29 import android.os.FileObserver;
     30 import android.os.Handler;
     31 import android.os.HandlerThread;
     32 import android.os.Message;
     33 import android.os.ResultReceiver;
     34 import android.os.ServiceManager;
     35 import android.os.ShellCallback;
     36 import android.os.ShellCommand;
     37 import android.os.UserHandle;
     38 import android.os.storage.StorageManager;
     39 import android.os.storage.VolumeInfo;
     40 import android.text.format.DateUtils;
     41 import android.util.ArrayMap;
     42 import android.util.DataUnit;
     43 import android.util.Slog;
     44 import android.util.StatsLog;
     45 
     46 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
     47 import com.android.internal.notification.SystemNotificationChannels;
     48 import com.android.internal.util.DumpUtils;
     49 import com.android.internal.util.IndentingPrintWriter;
     50 import com.android.server.EventLogTags;
     51 import com.android.server.SystemService;
     52 import com.android.server.pm.InstructionSets;
     53 import com.android.server.pm.PackageManagerService;
     54 
     55 import dalvik.system.VMRuntime;
     56 
     57 import java.io.File;
     58 import java.io.FileDescriptor;
     59 import java.io.IOException;
     60 import java.io.PrintWriter;
     61 import java.util.Objects;
     62 import java.util.UUID;
     63 import java.util.concurrent.atomic.AtomicInteger;
     64 
     65 /**
     66  * Service that monitors and maintains free space on storage volumes.
     67  * <p>
     68  * As the free space on a volume nears the threshold defined by
     69  * {@link StorageManager#getStorageLowBytes(File)}, this service will clear out
     70  * cached data to keep the disk from entering this low state.
     71  */
     72 public class DeviceStorageMonitorService extends SystemService {
     73     private static final String TAG = "DeviceStorageMonitorService";
     74 
     75     /**
     76      * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
     77      * Current int sequence number of the update.
     78      */
     79     public static final String EXTRA_SEQUENCE = "seq";
     80 
     81     private static final int MSG_CHECK = 1;
     82 
     83     private static final long DEFAULT_LOG_DELTA_BYTES = DataUnit.MEBIBYTES.toBytes(64);
     84     private static final long DEFAULT_CHECK_INTERVAL = DateUtils.MINUTE_IN_MILLIS;
     85 
     86     // com.android.internal.R.string.low_internal_storage_view_text_no_boot
     87     // hard codes 250MB in the message as the storage space required for the
     88     // boot image.
     89     private static final long BOOT_IMAGE_STORAGE_REQUIREMENT = DataUnit.MEBIBYTES.toBytes(250);
     90 
     91     private NotificationManager mNotifManager;
     92 
     93     /** Sequence number used for testing */
     94     private final AtomicInteger mSeq = new AtomicInteger(1);
     95     /** Forced level used for testing */
     96     private volatile int mForceLevel = State.LEVEL_UNKNOWN;
     97 
     98     /** Map from storage volume UUID to internal state */
     99     private final ArrayMap<UUID, State> mStates = new ArrayMap<>();
    100 
    101     /**
    102      * State for a specific storage volume, including the current "level" that
    103      * we've alerted the user and apps about.
    104      */
    105     private static class State {
    106         private static final int LEVEL_UNKNOWN = -1;
    107         private static final int LEVEL_NORMAL = 0;
    108         private static final int LEVEL_LOW = 1;
    109         private static final int LEVEL_FULL = 2;
    110 
    111         /** Last "level" that we alerted about */
    112         public int level = LEVEL_NORMAL;
    113         /** Last {@link File#getUsableSpace()} that we logged about */
    114         public long lastUsableBytes = Long.MAX_VALUE;
    115 
    116         /**
    117          * Test if the given level transition is "entering" a specific level.
    118          * <p>
    119          * As an example, a transition from {@link #LEVEL_NORMAL} to
    120          * {@link #LEVEL_FULL} is considered to "enter" both {@link #LEVEL_LOW}
    121          * and {@link #LEVEL_FULL}.
    122          */
    123         private static boolean isEntering(int level, int oldLevel, int newLevel) {
    124             return newLevel >= level && (oldLevel < level || oldLevel == LEVEL_UNKNOWN);
    125         }
    126 
    127         /**
    128          * Test if the given level transition is "leaving" a specific level.
    129          * <p>
    130          * As an example, a transition from {@link #LEVEL_FULL} to
    131          * {@link #LEVEL_NORMAL} is considered to "leave" both
    132          * {@link #LEVEL_FULL} and {@link #LEVEL_LOW}.
    133          */
    134         private static boolean isLeaving(int level, int oldLevel, int newLevel) {
    135             return newLevel < level && (oldLevel >= level || oldLevel == LEVEL_UNKNOWN);
    136         }
    137 
    138         private static String levelToString(int level) {
    139             switch (level) {
    140                 case State.LEVEL_UNKNOWN: return "UNKNOWN";
    141                 case State.LEVEL_NORMAL: return "NORMAL";
    142                 case State.LEVEL_LOW: return "LOW";
    143                 case State.LEVEL_FULL: return "FULL";
    144                 default: return Integer.toString(level);
    145             }
    146         }
    147     }
    148 
    149     private CacheFileDeletedObserver mCacheFileDeletedObserver;
    150 
    151     /**
    152      * This string is used for ServiceManager access to this class.
    153      */
    154     static final String SERVICE = "devicestoragemonitor";
    155 
    156     private static final String TV_NOTIFICATION_CHANNEL_ID = "devicestoragemonitor.tv";
    157 
    158     private final HandlerThread mHandlerThread;
    159     private final Handler mHandler;
    160 
    161     private State findOrCreateState(UUID uuid) {
    162         State state = mStates.get(uuid);
    163         if (state == null) {
    164             state = new State();
    165             mStates.put(uuid, state);
    166         }
    167         return state;
    168     }
    169 
    170     /**
    171      * Core logic that checks the storage state of every mounted private volume.
    172      * Since this can do heavy I/O, callers should invoke indirectly using
    173      * {@link #MSG_CHECK}.
    174      */
    175     @WorkerThread
    176     private void check() {
    177         final StorageManager storage = getContext().getSystemService(StorageManager.class);
    178         final int seq = mSeq.get();
    179 
    180         // Check every mounted private volume to see if they're low on space
    181         for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
    182             final File file = vol.getPath();
    183             final long fullBytes = storage.getStorageFullBytes(file);
    184             final long lowBytes = storage.getStorageLowBytes(file);
    185 
    186             // Automatically trim cached data when nearing the low threshold;
    187             // when it's within 150% of the threshold, we try trimming usage
    188             // back to 200% of the threshold.
    189             if (file.getUsableSpace() < (lowBytes * 3) / 2) {
    190                 final PackageManagerService pms = (PackageManagerService) ServiceManager
    191                         .getService("package");
    192                 try {
    193                     pms.freeStorage(vol.getFsUuid(), lowBytes * 2, 0);
    194                 } catch (IOException e) {
    195                     Slog.w(TAG, e);
    196                 }
    197             }
    198 
    199             // Send relevant broadcasts and show notifications based on any
    200             // recently noticed state transitions.
    201             final UUID uuid = StorageManager.convert(vol.getFsUuid());
    202             final State state = findOrCreateState(uuid);
    203             final long totalBytes = file.getTotalSpace();
    204             final long usableBytes = file.getUsableSpace();
    205 
    206             int oldLevel = state.level;
    207             int newLevel;
    208             if (mForceLevel != State.LEVEL_UNKNOWN) {
    209                 // When in testing mode, use unknown old level to force sending
    210                 // of any relevant broadcasts.
    211                 oldLevel = State.LEVEL_UNKNOWN;
    212                 newLevel = mForceLevel;
    213             } else if (usableBytes <= fullBytes) {
    214                 newLevel = State.LEVEL_FULL;
    215             } else if (usableBytes <= lowBytes) {
    216                 newLevel = State.LEVEL_LOW;
    217             } else if (StorageManager.UUID_DEFAULT.equals(uuid) && !isBootImageOnDisk()
    218                     && usableBytes < BOOT_IMAGE_STORAGE_REQUIREMENT) {
    219                 newLevel = State.LEVEL_LOW;
    220             } else {
    221                 newLevel = State.LEVEL_NORMAL;
    222             }
    223 
    224             // Log whenever we notice drastic storage changes
    225             if ((Math.abs(state.lastUsableBytes - usableBytes) > DEFAULT_LOG_DELTA_BYTES)
    226                     || oldLevel != newLevel) {
    227                 EventLogTags.writeStorageState(uuid.toString(), oldLevel, newLevel,
    228                         usableBytes, totalBytes);
    229                 state.lastUsableBytes = usableBytes;
    230             }
    231 
    232             updateNotifications(vol, oldLevel, newLevel);
    233             updateBroadcasts(vol, oldLevel, newLevel, seq);
    234 
    235             state.level = newLevel;
    236         }
    237 
    238         // Loop around to check again in future; we don't remove messages since
    239         // there might be an immediate request pending.
    240         if (!mHandler.hasMessages(MSG_CHECK)) {
    241             mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHECK),
    242                     DEFAULT_CHECK_INTERVAL);
    243         }
    244     }
    245 
    246     public DeviceStorageMonitorService(Context context) {
    247         super(context);
    248 
    249         mHandlerThread = new HandlerThread(TAG, android.os.Process.THREAD_PRIORITY_BACKGROUND);
    250         mHandlerThread.start();
    251 
    252         mHandler = new Handler(mHandlerThread.getLooper()) {
    253             @Override
    254             public void handleMessage(Message msg) {
    255                 switch (msg.what) {
    256                     case MSG_CHECK:
    257                         check();
    258                         return;
    259                 }
    260             }
    261         };
    262     }
    263 
    264     private static boolean isBootImageOnDisk() {
    265         for (String instructionSet : InstructionSets.getAllDexCodeInstructionSets()) {
    266             if (!VMRuntime.isBootClassPathOnDisk(instructionSet)) {
    267                 return false;
    268             }
    269         }
    270         return true;
    271     }
    272 
    273     @Override
    274     public void onStart() {
    275         final Context context = getContext();
    276         mNotifManager = context.getSystemService(NotificationManager.class);
    277 
    278         mCacheFileDeletedObserver = new CacheFileDeletedObserver();
    279         mCacheFileDeletedObserver.startWatching();
    280 
    281         // Ensure that the notification channel is set up
    282         PackageManager packageManager = context.getPackageManager();
    283         boolean isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
    284 
    285         if (isTv) {
    286             mNotifManager.createNotificationChannel(new NotificationChannel(
    287                     TV_NOTIFICATION_CHANNEL_ID,
    288                     context.getString(
    289                         com.android.internal.R.string.device_storage_monitor_notification_channel),
    290                     NotificationManager.IMPORTANCE_HIGH));
    291         }
    292 
    293         publishBinderService(SERVICE, mRemoteService);
    294         publishLocalService(DeviceStorageMonitorInternal.class, mLocalService);
    295 
    296         // Kick off pass to examine storage state
    297         mHandler.removeMessages(MSG_CHECK);
    298         mHandler.obtainMessage(MSG_CHECK).sendToTarget();
    299     }
    300 
    301     private final DeviceStorageMonitorInternal mLocalService = new DeviceStorageMonitorInternal() {
    302         @Override
    303         public void checkMemory() {
    304             // Kick off pass to examine storage state
    305             mHandler.removeMessages(MSG_CHECK);
    306             mHandler.obtainMessage(MSG_CHECK).sendToTarget();
    307         }
    308 
    309         @Override
    310         public boolean isMemoryLow() {
    311             return Environment.getDataDirectory().getUsableSpace() < getMemoryLowThreshold();
    312         }
    313 
    314         @Override
    315         public long getMemoryLowThreshold() {
    316             return getContext().getSystemService(StorageManager.class)
    317                     .getStorageLowBytes(Environment.getDataDirectory());
    318         }
    319     };
    320 
    321     private final Binder mRemoteService = new Binder() {
    322         @Override
    323         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
    324             if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
    325             dumpImpl(fd, pw, args);
    326         }
    327 
    328         @Override
    329         public void onShellCommand(FileDescriptor in, FileDescriptor out,
    330                 FileDescriptor err, String[] args, ShellCallback callback,
    331                 ResultReceiver resultReceiver) {
    332             (new Shell()).exec(this, in, out, err, args, callback, resultReceiver);
    333         }
    334     };
    335 
    336     class Shell extends ShellCommand {
    337         @Override
    338         public int onCommand(String cmd) {
    339             return onShellCommand(this, cmd);
    340         }
    341 
    342         @Override
    343         public void onHelp() {
    344             PrintWriter pw = getOutPrintWriter();
    345             dumpHelp(pw);
    346         }
    347     }
    348 
    349     static final int OPTION_FORCE_UPDATE = 1<<0;
    350 
    351     int parseOptions(Shell shell) {
    352         String opt;
    353         int opts = 0;
    354         while ((opt = shell.getNextOption()) != null) {
    355             if ("-f".equals(opt)) {
    356                 opts |= OPTION_FORCE_UPDATE;
    357             }
    358         }
    359         return opts;
    360     }
    361 
    362     int onShellCommand(Shell shell, String cmd) {
    363         if (cmd == null) {
    364             return shell.handleDefaultCommands(cmd);
    365         }
    366         PrintWriter pw = shell.getOutPrintWriter();
    367         switch (cmd) {
    368             case "force-low": {
    369                 int opts = parseOptions(shell);
    370                 getContext().enforceCallingOrSelfPermission(
    371                         android.Manifest.permission.DEVICE_POWER, null);
    372                 mForceLevel = State.LEVEL_LOW;
    373                 int seq = mSeq.incrementAndGet();
    374                 if ((opts & OPTION_FORCE_UPDATE) != 0) {
    375                     mHandler.removeMessages(MSG_CHECK);
    376                     mHandler.obtainMessage(MSG_CHECK).sendToTarget();
    377                     pw.println(seq);
    378                 }
    379             } break;
    380             case "force-not-low": {
    381                 int opts = parseOptions(shell);
    382                 getContext().enforceCallingOrSelfPermission(
    383                         android.Manifest.permission.DEVICE_POWER, null);
    384                 mForceLevel = State.LEVEL_NORMAL;
    385                 int seq = mSeq.incrementAndGet();
    386                 if ((opts & OPTION_FORCE_UPDATE) != 0) {
    387                     mHandler.removeMessages(MSG_CHECK);
    388                     mHandler.obtainMessage(MSG_CHECK).sendToTarget();
    389                     pw.println(seq);
    390                 }
    391             } break;
    392             case "reset": {
    393                 int opts = parseOptions(shell);
    394                 getContext().enforceCallingOrSelfPermission(
    395                         android.Manifest.permission.DEVICE_POWER, null);
    396                 mForceLevel = State.LEVEL_UNKNOWN;
    397                 int seq = mSeq.incrementAndGet();
    398                 if ((opts & OPTION_FORCE_UPDATE) != 0) {
    399                     mHandler.removeMessages(MSG_CHECK);
    400                     mHandler.obtainMessage(MSG_CHECK).sendToTarget();
    401                     pw.println(seq);
    402                 }
    403             } break;
    404             default:
    405                 return shell.handleDefaultCommands(cmd);
    406         }
    407         return 0;
    408     }
    409 
    410     static void dumpHelp(PrintWriter pw) {
    411         pw.println("Device storage monitor service (devicestoragemonitor) commands:");
    412         pw.println("  help");
    413         pw.println("    Print this help text.");
    414         pw.println("  force-low [-f]");
    415         pw.println("    Force storage to be low, freezing storage state.");
    416         pw.println("    -f: force a storage change broadcast be sent, prints new sequence.");
    417         pw.println("  force-not-low [-f]");
    418         pw.println("    Force storage to not be low, freezing storage state.");
    419         pw.println("    -f: force a storage change broadcast be sent, prints new sequence.");
    420         pw.println("  reset [-f]");
    421         pw.println("    Unfreeze storage state, returning to current real values.");
    422         pw.println("    -f: force a storage change broadcast be sent, prints new sequence.");
    423     }
    424 
    425     void dumpImpl(FileDescriptor fd, PrintWriter _pw, String[] args) {
    426         final IndentingPrintWriter pw = new IndentingPrintWriter(_pw, "  ");
    427         if (args == null || args.length == 0 || "-a".equals(args[0])) {
    428             pw.println("Known volumes:");
    429             pw.increaseIndent();
    430             for (int i = 0; i < mStates.size(); i++) {
    431                 final UUID uuid = mStates.keyAt(i);
    432                 final State state = mStates.valueAt(i);
    433                 if (StorageManager.UUID_DEFAULT.equals(uuid)) {
    434                     pw.println("Default:");
    435                 } else {
    436                     pw.println(uuid + ":");
    437                 }
    438                 pw.increaseIndent();
    439                 pw.printPair("level", State.levelToString(state.level));
    440                 pw.printPair("lastUsableBytes", state.lastUsableBytes);
    441                 pw.println();
    442                 pw.decreaseIndent();
    443             }
    444             pw.decreaseIndent();
    445             pw.println();
    446 
    447             pw.printPair("mSeq", mSeq.get());
    448             pw.printPair("mForceState", State.levelToString(mForceLevel));
    449             pw.println();
    450             pw.println();
    451 
    452         } else {
    453             Shell shell = new Shell();
    454             shell.exec(mRemoteService, null, fd, null, args, null, new ResultReceiver(null));
    455         }
    456     }
    457 
    458     private void updateNotifications(VolumeInfo vol, int oldLevel, int newLevel) {
    459         final Context context = getContext();
    460         final UUID uuid = StorageManager.convert(vol.getFsUuid());
    461 
    462         if (State.isEntering(State.LEVEL_LOW, oldLevel, newLevel)) {
    463             Intent lowMemIntent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
    464             lowMemIntent.putExtra(StorageManager.EXTRA_UUID, uuid);
    465             lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    466 
    467             final CharSequence title = context.getText(
    468                     com.android.internal.R.string.low_internal_storage_view_title);
    469 
    470             final CharSequence details;
    471             if (StorageManager.UUID_DEFAULT.equals(uuid)) {
    472                 details = context.getText(isBootImageOnDisk()
    473                         ? com.android.internal.R.string.low_internal_storage_view_text
    474                         : com.android.internal.R.string.low_internal_storage_view_text_no_boot);
    475             } else {
    476                 details = context.getText(
    477                         com.android.internal.R.string.low_internal_storage_view_text);
    478             }
    479 
    480             PendingIntent intent = PendingIntent.getActivityAsUser(context, 0, lowMemIntent, 0,
    481                     null, UserHandle.CURRENT);
    482             Notification notification =
    483                     new Notification.Builder(context, SystemNotificationChannels.ALERTS)
    484                             .setSmallIcon(com.android.internal.R.drawable.stat_notify_disk_full)
    485                             .setTicker(title)
    486                             .setColor(context.getColor(
    487                                 com.android.internal.R.color.system_notification_accent_color))
    488                             .setContentTitle(title)
    489                             .setContentText(details)
    490                             .setContentIntent(intent)
    491                             .setStyle(new Notification.BigTextStyle()
    492                                   .bigText(details))
    493                             .setVisibility(Notification.VISIBILITY_PUBLIC)
    494                             .setCategory(Notification.CATEGORY_SYSTEM)
    495                             .extend(new Notification.TvExtender()
    496                                     .setChannelId(TV_NOTIFICATION_CHANNEL_ID))
    497                             .build();
    498             notification.flags |= Notification.FLAG_NO_CLEAR;
    499             mNotifManager.notifyAsUser(uuid.toString(), SystemMessage.NOTE_LOW_STORAGE,
    500                     notification, UserHandle.ALL);
    501             StatsLog.write(StatsLog.LOW_STORAGE_STATE_CHANGED,
    502                     Objects.toString(vol.getDescription()),
    503                     StatsLog.LOW_STORAGE_STATE_CHANGED__STATE__ON);
    504         } else if (State.isLeaving(State.LEVEL_LOW, oldLevel, newLevel)) {
    505             mNotifManager.cancelAsUser(uuid.toString(), SystemMessage.NOTE_LOW_STORAGE,
    506                     UserHandle.ALL);
    507             StatsLog.write(StatsLog.LOW_STORAGE_STATE_CHANGED,
    508                     Objects.toString(vol.getDescription()),
    509                     StatsLog.LOW_STORAGE_STATE_CHANGED__STATE__OFF);
    510         }
    511     }
    512 
    513     private void updateBroadcasts(VolumeInfo vol, int oldLevel, int newLevel, int seq) {
    514         if (!Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, vol.getFsUuid())) {
    515             // We don't currently send broadcasts for secondary volumes
    516             return;
    517         }
    518 
    519         final Intent lowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW)
    520                 .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
    521                         | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
    522                         | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS)
    523                 .putExtra(EXTRA_SEQUENCE, seq);
    524         final Intent notLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK)
    525                 .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
    526                         | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND
    527                         | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS)
    528                 .putExtra(EXTRA_SEQUENCE, seq);
    529 
    530         if (State.isEntering(State.LEVEL_LOW, oldLevel, newLevel)) {
    531             getContext().sendStickyBroadcastAsUser(lowIntent, UserHandle.ALL);
    532         } else if (State.isLeaving(State.LEVEL_LOW, oldLevel, newLevel)) {
    533             getContext().removeStickyBroadcastAsUser(lowIntent, UserHandle.ALL);
    534             getContext().sendBroadcastAsUser(notLowIntent, UserHandle.ALL);
    535         }
    536 
    537         final Intent fullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL)
    538                 .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)
    539                 .putExtra(EXTRA_SEQUENCE, seq);
    540         final Intent notFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL)
    541                 .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT)
    542                 .putExtra(EXTRA_SEQUENCE, seq);
    543 
    544         if (State.isEntering(State.LEVEL_FULL, oldLevel, newLevel)) {
    545             getContext().sendStickyBroadcastAsUser(fullIntent, UserHandle.ALL);
    546         } else if (State.isLeaving(State.LEVEL_FULL, oldLevel, newLevel)) {
    547             getContext().removeStickyBroadcastAsUser(fullIntent, UserHandle.ALL);
    548             getContext().sendBroadcastAsUser(notFullIntent, UserHandle.ALL);
    549         }
    550     }
    551 
    552     private static class CacheFileDeletedObserver extends FileObserver {
    553         public CacheFileDeletedObserver() {
    554             super(Environment.getDownloadCacheDirectory().getAbsolutePath(), FileObserver.DELETE);
    555         }
    556 
    557         @Override
    558         public void onEvent(int event, String path) {
    559             EventLogTags.writeCacheFileDeleted(path);
    560         }
    561     }
    562 }
    563