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