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