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.HandlerThread; 33 import android.os.Message; 34 import android.os.ResultReceiver; 35 import android.os.ServiceManager; 36 import android.os.ShellCallback; 37 import android.os.ShellCommand; 38 import android.os.UserHandle; 39 import android.os.storage.StorageManager; 40 import android.os.storage.VolumeInfo; 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.IndentingPrintWriter; 50 import com.android.server.EventLogTags; 51 import com.android.server.IoThread; 52 import com.android.server.SystemService; 53 import com.android.server.pm.InstructionSets; 54 import com.android.server.pm.PackageManagerService; 55 56 import dalvik.system.VMRuntime; 57 58 import java.io.File; 59 import java.io.FileDescriptor; 60 import java.io.IOException; 61 import java.io.PrintWriter; 62 import java.util.Objects; 63 import java.util.UUID; 64 import java.util.concurrent.atomic.AtomicInteger; 65 66 /** 67 * Service that monitors and maintains free space on storage volumes. 68 * <p> 69 * As the free space on a volume nears the threshold defined by 70 * {@link StorageManager#getStorageLowBytes(File)}, this service will clear out 71 * cached data to keep the disk from entering this low state. 72 */ 73 public class DeviceStorageMonitorService extends SystemService { 74 private static final String TAG = "DeviceStorageMonitorService"; 75 76 /** 77 * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}: 78 * Current int sequence number of the update. 79 */ 80 public static final String EXTRA_SEQUENCE = "seq"; 81 82 private static final int MSG_CHECK = 1; 83 84 private static final long DEFAULT_LOG_DELTA_BYTES = DataUnit.MEBIBYTES.toBytes(64); 85 private static final long DEFAULT_CHECK_INTERVAL = DateUtils.MINUTE_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 */ 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 */ 135 private static boolean isLeaving(int level, int oldLevel, int newLevel) { 136 return newLevel < level && (oldLevel >= level || oldLevel == LEVEL_UNKNOWN); 137 } 138 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 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. 173 * Since this can do heavy I/O, callers should invoke indirectly using 174 * {@link #MSG_CHECK}. 175 */ 176 @WorkerThread 177 private void check() { 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 PackageManagerService pms = (PackageManagerService) ServiceManager 192 .getService("package"); 193 try { 194 pms.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) && !isBootImageOnDisk() 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)) { 242 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CHECK), 243 DEFAULT_CHECK_INTERVAL); 244 } 245 } 246 247 public DeviceStorageMonitorService(Context context) { 248 super(context); 249 250 mHandlerThread = new HandlerThread(TAG, android.os.Process.THREAD_PRIORITY_BACKGROUND); 251 mHandlerThread.start(); 252 253 mHandler = new Handler(mHandlerThread.getLooper()) { 254 @Override 255 public void handleMessage(Message msg) { 256 switch (msg.what) { 257 case MSG_CHECK: 258 check(); 259 return; 260 } 261 } 262 }; 263 } 264 265 private static boolean isBootImageOnDisk() { 266 for (String instructionSet : InstructionSets.getAllDexCodeInstructionSets()) { 267 if (!VMRuntime.isBootClassPathOnDisk(instructionSet)) { 268 return false; 269 } 270 } 271 return true; 272 } 273 274 @Override 275 public void onStart() { 276 final Context context = getContext(); 277 mNotifManager = context.getSystemService(NotificationManager.class); 278 279 mCacheFileDeletedObserver = new CacheFileDeletedObserver(); 280 mCacheFileDeletedObserver.startWatching(); 281 282 // Ensure that the notification channel is set up 283 PackageManager packageManager = context.getPackageManager(); 284 boolean isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK); 285 286 if (isTv) { 287 mNotifManager.createNotificationChannel(new NotificationChannel( 288 TV_NOTIFICATION_CHANNEL_ID, 289 context.getString( 290 com.android.internal.R.string.device_storage_monitor_notification_channel), 291 NotificationManager.IMPORTANCE_HIGH)); 292 } 293 294 publishBinderService(SERVICE, mRemoteService); 295 publishLocalService(DeviceStorageMonitorInternal.class, mLocalService); 296 297 // Kick off pass to examine storage state 298 mHandler.removeMessages(MSG_CHECK); 299 mHandler.obtainMessage(MSG_CHECK).sendToTarget(); 300 } 301 302 private final DeviceStorageMonitorInternal mLocalService = new DeviceStorageMonitorInternal() { 303 @Override 304 public void checkMemory() { 305 // Kick off pass to examine storage state 306 mHandler.removeMessages(MSG_CHECK); 307 mHandler.obtainMessage(MSG_CHECK).sendToTarget(); 308 } 309 310 @Override 311 public boolean isMemoryLow() { 312 return Environment.getDataDirectory().getUsableSpace() < getMemoryLowThreshold(); 313 } 314 315 @Override 316 public long getMemoryLowThreshold() { 317 return getContext().getSystemService(StorageManager.class) 318 .getStorageLowBytes(Environment.getDataDirectory()); 319 } 320 }; 321 322 private final Binder mRemoteService = new Binder() { 323 @Override 324 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 325 if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return; 326 dumpImpl(fd, pw, args); 327 } 328 329 @Override 330 public void onShellCommand(FileDescriptor in, FileDescriptor out, 331 FileDescriptor err, String[] args, ShellCallback callback, 332 ResultReceiver resultReceiver) { 333 (new Shell()).exec(this, in, out, err, args, callback, resultReceiver); 334 } 335 }; 336 337 class Shell extends ShellCommand { 338 @Override 339 public int onCommand(String cmd) { 340 return onShellCommand(this, cmd); 341 } 342 343 @Override 344 public void onHelp() { 345 PrintWriter pw = getOutPrintWriter(); 346 dumpHelp(pw); 347 } 348 } 349 350 static final int OPTION_FORCE_UPDATE = 1<<0; 351 352 int parseOptions(Shell shell) { 353 String opt; 354 int opts = 0; 355 while ((opt = shell.getNextOption()) != null) { 356 if ("-f".equals(opt)) { 357 opts |= OPTION_FORCE_UPDATE; 358 } 359 } 360 return opts; 361 } 362 363 int onShellCommand(Shell shell, String cmd) { 364 if (cmd == null) { 365 return shell.handleDefaultCommands(cmd); 366 } 367 PrintWriter pw = shell.getOutPrintWriter(); 368 switch (cmd) { 369 case "force-low": { 370 int opts = parseOptions(shell); 371 getContext().enforceCallingOrSelfPermission( 372 android.Manifest.permission.DEVICE_POWER, null); 373 mForceLevel = State.LEVEL_LOW; 374 int seq = mSeq.incrementAndGet(); 375 if ((opts & OPTION_FORCE_UPDATE) != 0) { 376 mHandler.removeMessages(MSG_CHECK); 377 mHandler.obtainMessage(MSG_CHECK).sendToTarget(); 378 pw.println(seq); 379 } 380 } break; 381 case "force-not-low": { 382 int opts = parseOptions(shell); 383 getContext().enforceCallingOrSelfPermission( 384 android.Manifest.permission.DEVICE_POWER, null); 385 mForceLevel = State.LEVEL_NORMAL; 386 int seq = mSeq.incrementAndGet(); 387 if ((opts & OPTION_FORCE_UPDATE) != 0) { 388 mHandler.removeMessages(MSG_CHECK); 389 mHandler.obtainMessage(MSG_CHECK).sendToTarget(); 390 pw.println(seq); 391 } 392 } break; 393 case "reset": { 394 int opts = parseOptions(shell); 395 getContext().enforceCallingOrSelfPermission( 396 android.Manifest.permission.DEVICE_POWER, null); 397 mForceLevel = State.LEVEL_UNKNOWN; 398 int seq = mSeq.incrementAndGet(); 399 if ((opts & OPTION_FORCE_UPDATE) != 0) { 400 mHandler.removeMessages(MSG_CHECK); 401 mHandler.obtainMessage(MSG_CHECK).sendToTarget(); 402 pw.println(seq); 403 } 404 } break; 405 default: 406 return shell.handleDefaultCommands(cmd); 407 } 408 return 0; 409 } 410 411 static void dumpHelp(PrintWriter pw) { 412 pw.println("Device storage monitor service (devicestoragemonitor) commands:"); 413 pw.println(" help"); 414 pw.println(" Print this help text."); 415 pw.println(" force-low [-f]"); 416 pw.println(" Force storage to be low, freezing storage state."); 417 pw.println(" -f: force a storage change broadcast be sent, prints new sequence."); 418 pw.println(" force-not-low [-f]"); 419 pw.println(" Force storage to not be low, freezing storage state."); 420 pw.println(" -f: force a storage change broadcast be sent, prints new sequence."); 421 pw.println(" reset [-f]"); 422 pw.println(" Unfreeze storage state, returning to current real values."); 423 pw.println(" -f: force a storage change broadcast be sent, prints new sequence."); 424 } 425 426 void dumpImpl(FileDescriptor fd, PrintWriter _pw, String[] args) { 427 final IndentingPrintWriter pw = new IndentingPrintWriter(_pw, " "); 428 if (args == null || args.length == 0 || "-a".equals(args[0])) { 429 pw.println("Known volumes:"); 430 pw.increaseIndent(); 431 for (int i = 0; i < mStates.size(); i++) { 432 final UUID uuid = mStates.keyAt(i); 433 final State state = mStates.valueAt(i); 434 if (StorageManager.UUID_DEFAULT.equals(uuid)) { 435 pw.println("Default:"); 436 } else { 437 pw.println(uuid + ":"); 438 } 439 pw.increaseIndent(); 440 pw.printPair("level", State.levelToString(state.level)); 441 pw.printPair("lastUsableBytes", state.lastUsableBytes); 442 pw.println(); 443 pw.decreaseIndent(); 444 } 445 pw.decreaseIndent(); 446 pw.println(); 447 448 pw.printPair("mSeq", mSeq.get()); 449 pw.printPair("mForceState", State.levelToString(mForceLevel)); 450 pw.println(); 451 pw.println(); 452 453 } else { 454 Shell shell = new Shell(); 455 shell.exec(mRemoteService, null, fd, null, args, null, new ResultReceiver(null)); 456 } 457 } 458 459 private void updateNotifications(VolumeInfo vol, int oldLevel, int newLevel) { 460 final Context context = getContext(); 461 final UUID uuid = StorageManager.convert(vol.getFsUuid()); 462 463 if (State.isEntering(State.LEVEL_LOW, oldLevel, newLevel)) { 464 Intent lowMemIntent = new Intent(StorageManager.ACTION_MANAGE_STORAGE); 465 lowMemIntent.putExtra(StorageManager.EXTRA_UUID, uuid); 466 lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 467 468 final CharSequence title = context.getText( 469 com.android.internal.R.string.low_internal_storage_view_title); 470 471 final CharSequence details; 472 if (StorageManager.UUID_DEFAULT.equals(uuid)) { 473 details = context.getText(isBootImageOnDisk() 474 ? com.android.internal.R.string.low_internal_storage_view_text 475 : com.android.internal.R.string.low_internal_storage_view_text_no_boot); 476 } else { 477 details = context.getText( 478 com.android.internal.R.string.low_internal_storage_view_text); 479 } 480 481 PendingIntent intent = PendingIntent.getActivityAsUser(context, 0, lowMemIntent, 0, 482 null, UserHandle.CURRENT); 483 Notification notification = 484 new Notification.Builder(context, SystemNotificationChannels.ALERTS) 485 .setSmallIcon(com.android.internal.R.drawable.stat_notify_disk_full) 486 .setTicker(title) 487 .setColor(context.getColor( 488 com.android.internal.R.color.system_notification_accent_color)) 489 .setContentTitle(title) 490 .setContentText(details) 491 .setContentIntent(intent) 492 .setStyle(new Notification.BigTextStyle() 493 .bigText(details)) 494 .setVisibility(Notification.VISIBILITY_PUBLIC) 495 .setCategory(Notification.CATEGORY_SYSTEM) 496 .extend(new Notification.TvExtender() 497 .setChannelId(TV_NOTIFICATION_CHANNEL_ID)) 498 .build(); 499 notification.flags |= Notification.FLAG_NO_CLEAR; 500 mNotifManager.notifyAsUser(uuid.toString(), SystemMessage.NOTE_LOW_STORAGE, 501 notification, UserHandle.ALL); 502 } else if (State.isLeaving(State.LEVEL_LOW, oldLevel, newLevel)) { 503 mNotifManager.cancelAsUser(uuid.toString(), SystemMessage.NOTE_LOW_STORAGE, 504 UserHandle.ALL); 505 } 506 } 507 508 private void updateBroadcasts(VolumeInfo vol, int oldLevel, int newLevel, int seq) { 509 if (!Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, vol.getFsUuid())) { 510 // We don't currently send broadcasts for secondary volumes 511 return; 512 } 513 514 final Intent lowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_LOW) 515 .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT 516 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND 517 | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS) 518 .putExtra(EXTRA_SEQUENCE, seq); 519 final Intent notLowIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_OK) 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 525 if (State.isEntering(State.LEVEL_LOW, oldLevel, newLevel)) { 526 getContext().sendStickyBroadcastAsUser(lowIntent, UserHandle.ALL); 527 } else if (State.isLeaving(State.LEVEL_LOW, oldLevel, newLevel)) { 528 getContext().removeStickyBroadcastAsUser(lowIntent, UserHandle.ALL); 529 getContext().sendBroadcastAsUser(notLowIntent, UserHandle.ALL); 530 } 531 532 final Intent fullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_FULL) 533 .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT) 534 .putExtra(EXTRA_SEQUENCE, seq); 535 final Intent notFullIntent = new Intent(Intent.ACTION_DEVICE_STORAGE_NOT_FULL) 536 .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT) 537 .putExtra(EXTRA_SEQUENCE, seq); 538 539 if (State.isEntering(State.LEVEL_FULL, oldLevel, newLevel)) { 540 getContext().sendStickyBroadcastAsUser(fullIntent, UserHandle.ALL); 541 } else if (State.isLeaving(State.LEVEL_FULL, oldLevel, newLevel)) { 542 getContext().removeStickyBroadcastAsUser(fullIntent, UserHandle.ALL); 543 getContext().sendBroadcastAsUser(notFullIntent, UserHandle.ALL); 544 } 545 } 546 547 private static class CacheFileDeletedObserver extends FileObserver { 548 public CacheFileDeletedObserver() { 549 super(Environment.getDownloadCacheDirectory().getAbsolutePath(), FileObserver.DELETE); 550 } 551 552 @Override 553 public void onEvent(int event, String path) { 554 EventLogTags.writeCacheFileDeleted(path); 555 } 556 } 557 } 558