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 
45 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
46 import com.android.internal.notification.SystemNotificationChannels;
47 import com.android.internal.util.DumpUtils;
48 import com.android.internal.util.FrameworkStatsLog;
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             final StorageManager storage = getContext().getSystemService(StorageManager.class);
417             pw.println("Known volumes:");
418             pw.increaseIndent();
419             for (int i = 0; i < mStates.size(); i++) {
420                 final UUID uuid = mStates.keyAt(i);
421                 final State state = mStates.valueAt(i);
422                 if (StorageManager.UUID_DEFAULT.equals(uuid)) {
423                     pw.println("Default:");
424                 } else {
425                     pw.println(uuid + ":");
426                 }
427                 pw.increaseIndent();
428                 pw.printPair("level", State.levelToString(state.level));
429                 pw.printPair("lastUsableBytes", state.lastUsableBytes);
430                 pw.println();
431                 for (VolumeInfo vol : storage.getWritablePrivateVolumes()) {
432                     final File file = vol.getPath();
433                     final UUID innerUuid = StorageManager.convert(vol.getFsUuid());
434                     if (Objects.equals(uuid, innerUuid)) {
435                         pw.print("lowBytes=");
436                         pw.print(storage.getStorageLowBytes(file));
437                         pw.print(" fullBytes=");
438                         pw.println(storage.getStorageFullBytes(file));
439                         pw.print("path=");
440                         pw.println(file);
441                         break;
442                     }
443                 }
444                 pw.decreaseIndent();
445             }
446             pw.decreaseIndent();
447             pw.println();
448 
449             pw.printPair("mSeq", mSeq.get());
450             pw.printPair("mForceState", State.levelToString(mForceLevel));
451             pw.println();
452             pw.println();
453 
454         } else {
455             Shell shell = new Shell();
456             shell.exec(mRemoteService, null, fd, null, args, null, new ResultReceiver(null));
457         }
458     }
459 
updateNotifications(VolumeInfo vol, int oldLevel, int newLevel)460     private void updateNotifications(VolumeInfo vol, int oldLevel, int newLevel) {
461         final Context context = getContext();
462         final UUID uuid = StorageManager.convert(vol.getFsUuid());
463 
464         if (State.isEntering(State.LEVEL_LOW, oldLevel, newLevel)) {
465             Intent lowMemIntent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
466             lowMemIntent.putExtra(StorageManager.EXTRA_UUID, uuid);
467             lowMemIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
468 
469             final CharSequence title = context.getText(
470                     com.android.internal.R.string.low_internal_storage_view_title);
471 
472             final CharSequence details = context.getText(
473                     com.android.internal.R.string.low_internal_storage_view_text);
474 
475             PendingIntent intent = PendingIntent.getActivityAsUser(context, 0, lowMemIntent,
476                     PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
477             Notification notification =
478                     new Notification.Builder(context, SystemNotificationChannels.ALERTS)
479                             .setSmallIcon(com.android.internal.R.drawable.stat_notify_disk_full)
480                             .setTicker(title)
481                             .setColor(context.getColor(
482                                 com.android.internal.R.color.system_notification_accent_color))
483                             .setContentTitle(title)
484                             .setContentText(details)
485                             .setContentIntent(intent)
486                             .setStyle(new Notification.BigTextStyle()
487                                   .bigText(details))
488                             .setVisibility(Notification.VISIBILITY_PUBLIC)
489                             .setCategory(Notification.CATEGORY_SYSTEM)
490                             .extend(new Notification.TvExtender()
491                                     .setChannelId(TV_NOTIFICATION_CHANNEL_ID))
492                             .build();
493             notification.flags |= Notification.FLAG_NO_CLEAR;
494             mNotifManager.notifyAsUser(uuid.toString(), SystemMessage.NOTE_LOW_STORAGE,
495                     notification, UserHandle.ALL);
496             FrameworkStatsLog.write(FrameworkStatsLog.LOW_STORAGE_STATE_CHANGED,
497                     Objects.toString(vol.getDescription()),
498                     FrameworkStatsLog.LOW_STORAGE_STATE_CHANGED__STATE__ON);
499         } else if (State.isLeaving(State.LEVEL_LOW, oldLevel, newLevel)) {
500             mNotifManager.cancelAsUser(uuid.toString(), SystemMessage.NOTE_LOW_STORAGE,
501                     UserHandle.ALL);
502             FrameworkStatsLog.write(FrameworkStatsLog.LOW_STORAGE_STATE_CHANGED,
503                     Objects.toString(vol.getDescription()),
504                     FrameworkStatsLog.LOW_STORAGE_STATE_CHANGED__STATE__OFF);
505         }
506     }
507 
updateBroadcasts(VolumeInfo vol, int oldLevel, int newLevel, int seq)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 {
CacheFileDeletedObserver()548         public CacheFileDeletedObserver() {
549             super(Environment.getDownloadCacheDirectory().getAbsolutePath(), FileObserver.DELETE);
550         }
551 
552         @Override
onEvent(int event, String path)553         public void onEvent(int event, String path) {
554             EventLogTags.writeCacheFileDeleted(path);
555         }
556     }
557 }
558