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