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