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