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 */ isEntering(int level, int oldLevel, int newLevel)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 */ isLeaving(int level, int oldLevel, int newLevel)134 private static boolean isLeaving(int level, int oldLevel, int newLevel) { 135 return newLevel < level && (oldLevel >= level || oldLevel == LEVEL_UNKNOWN); 136 } 137 levelToString(int level)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 findOrCreateState(UUID uuid)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 check()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 DeviceStorageMonitorService(Context context)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 isBootImageOnDisk()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 onStart()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 onCommand(String cmd)338 public int onCommand(String cmd) { 339 return onShellCommand(this, cmd); 340 } 341 342 @Override onHelp()343 public void onHelp() { 344 PrintWriter pw = getOutPrintWriter(); 345 dumpHelp(pw); 346 } 347 } 348 349 static final int OPTION_FORCE_UPDATE = 1<<0; 350 parseOptions(Shell shell)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 onShellCommand(Shell shell, String cmd)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 dumpHelp(PrintWriter pw)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 dumpImpl(FileDescriptor fd, PrintWriter _pw, String[] args)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 updateNotifications(VolumeInfo vol, int oldLevel, int newLevel)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 updateBroadcasts(VolumeInfo vol, int oldLevel, int newLevel, int seq)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 { CacheFileDeletedObserver()553 public CacheFileDeletedObserver() { 554 super(Environment.getDownloadCacheDirectory().getAbsolutePath(), FileObserver.DELETE); 555 } 556 557 @Override onEvent(int event, String path)558 public void onEvent(int event, String path) { 559 EventLogTags.writeCacheFileDeleted(path); 560 } 561 } 562 } 563