Home | History | Annotate | Download | only in storage
      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