1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE2.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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package com.android.server.storage;
18 
19 import android.annotation.IntDef;
20 import android.app.usage.ExternalStorageStats;
21 import android.app.usage.StorageStatsManager;
22 import android.content.Context;
23 import android.content.pm.PackageManager;
24 import android.os.UserHandle;
25 import android.os.storage.StorageManager;
26 import android.os.storage.VolumeInfo;
27 import android.util.ArrayMap;
28 
29 import java.io.File;
30 import java.io.IOException;
31 import java.lang.annotation.Retention;
32 import java.lang.annotation.RetentionPolicy;
33 import java.util.Map;
34 
35 /**
36  * FileCollector walks over a directory and categorizes storage usage by their type.
37  */
38 public class FileCollector {
39     private static final int UNRECOGNIZED = -1;
40     private static final int IMAGES = 0;
41     private static final int VIDEO = 1;
42     private static final int AUDIO = 2;
43     @Retention(RetentionPolicy.SOURCE)
44     @IntDef({
45             UNRECOGNIZED,
46             IMAGES,
47             VIDEO,
48             AUDIO })
49     private @interface FileTypes {}
50 
51 
52     private static final Map<String, Integer> EXTENSION_MAP = new ArrayMap<String, Integer>();
53     static {
54         // Audio
55         EXTENSION_MAP.put("aac", AUDIO);
56         EXTENSION_MAP.put("amr", AUDIO);
57         EXTENSION_MAP.put("awb", AUDIO);
58         EXTENSION_MAP.put("snd", AUDIO);
59         EXTENSION_MAP.put("flac", AUDIO);
60         EXTENSION_MAP.put("mp3", AUDIO);
61         EXTENSION_MAP.put("mpga", AUDIO);
62         EXTENSION_MAP.put("mpega", AUDIO);
63         EXTENSION_MAP.put("mp2", AUDIO);
64         EXTENSION_MAP.put("m4a", AUDIO);
65         EXTENSION_MAP.put("aif", AUDIO);
66         EXTENSION_MAP.put("aiff", AUDIO);
67         EXTENSION_MAP.put("aifc", AUDIO);
68         EXTENSION_MAP.put("gsm", AUDIO);
69         EXTENSION_MAP.put("mka", AUDIO);
70         EXTENSION_MAP.put("m3u", AUDIO);
71         EXTENSION_MAP.put("wma", AUDIO);
72         EXTENSION_MAP.put("wax", AUDIO);
73         EXTENSION_MAP.put("ra", AUDIO);
74         EXTENSION_MAP.put("rm", AUDIO);
75         EXTENSION_MAP.put("ram", AUDIO);
76         EXTENSION_MAP.put("pls", AUDIO);
77         EXTENSION_MAP.put("sd2", AUDIO);
78         EXTENSION_MAP.put("wav", AUDIO);
79         EXTENSION_MAP.put("ogg", AUDIO);
80         EXTENSION_MAP.put("oga", AUDIO);
81         // Video
82         EXTENSION_MAP.put("3gpp", VIDEO);
83         EXTENSION_MAP.put("3gp", VIDEO);
84         EXTENSION_MAP.put("3gpp2", VIDEO);
85         EXTENSION_MAP.put("3g2", VIDEO);
86         EXTENSION_MAP.put("avi", VIDEO);
87         EXTENSION_MAP.put("dl", VIDEO);
88         EXTENSION_MAP.put("dif", VIDEO);
89         EXTENSION_MAP.put("dv", VIDEO);
90         EXTENSION_MAP.put("fli", VIDEO);
91         EXTENSION_MAP.put("m4v", VIDEO);
92         EXTENSION_MAP.put("ts", VIDEO);
93         EXTENSION_MAP.put("mpeg", VIDEO);
94         EXTENSION_MAP.put("mpg", VIDEO);
95         EXTENSION_MAP.put("mpe", VIDEO);
96         EXTENSION_MAP.put("mp4", VIDEO);
97         EXTENSION_MAP.put("vob", VIDEO);
98         EXTENSION_MAP.put("qt", VIDEO);
99         EXTENSION_MAP.put("mov", VIDEO);
100         EXTENSION_MAP.put("mxu", VIDEO);
101         EXTENSION_MAP.put("webm", VIDEO);
102         EXTENSION_MAP.put("lsf", VIDEO);
103         EXTENSION_MAP.put("lsx", VIDEO);
104         EXTENSION_MAP.put("mkv", VIDEO);
105         EXTENSION_MAP.put("mng", VIDEO);
106         EXTENSION_MAP.put("asf", VIDEO);
107         EXTENSION_MAP.put("asx", VIDEO);
108         EXTENSION_MAP.put("wm", VIDEO);
109         EXTENSION_MAP.put("wmv", VIDEO);
110         EXTENSION_MAP.put("wmx", VIDEO);
111         EXTENSION_MAP.put("wvx", VIDEO);
112         EXTENSION_MAP.put("movie", VIDEO);
113         EXTENSION_MAP.put("wrf", VIDEO);
114         // Images
115         EXTENSION_MAP.put("bmp", IMAGES);
116         EXTENSION_MAP.put("gif", IMAGES);
117         EXTENSION_MAP.put("jpg", IMAGES);
118         EXTENSION_MAP.put("jpeg", IMAGES);
119         EXTENSION_MAP.put("jpe", IMAGES);
120         EXTENSION_MAP.put("pcx", IMAGES);
121         EXTENSION_MAP.put("png", IMAGES);
122         EXTENSION_MAP.put("svg", IMAGES);
123         EXTENSION_MAP.put("svgz", IMAGES);
124         EXTENSION_MAP.put("tiff", IMAGES);
125         EXTENSION_MAP.put("tif", IMAGES);
126         EXTENSION_MAP.put("wbmp", IMAGES);
127         EXTENSION_MAP.put("webp", IMAGES);
128         EXTENSION_MAP.put("dng", IMAGES);
129         EXTENSION_MAP.put("cr2", IMAGES);
130         EXTENSION_MAP.put("ras", IMAGES);
131         EXTENSION_MAP.put("art", IMAGES);
132         EXTENSION_MAP.put("jng", IMAGES);
133         EXTENSION_MAP.put("nef", IMAGES);
134         EXTENSION_MAP.put("nrw", IMAGES);
135         EXTENSION_MAP.put("orf", IMAGES);
136         EXTENSION_MAP.put("rw2", IMAGES);
137         EXTENSION_MAP.put("pef", IMAGES);
138         EXTENSION_MAP.put("psd", IMAGES);
139         EXTENSION_MAP.put("pnm", IMAGES);
140         EXTENSION_MAP.put("pbm", IMAGES);
141         EXTENSION_MAP.put("pgm", IMAGES);
142         EXTENSION_MAP.put("ppm", IMAGES);
143         EXTENSION_MAP.put("srw", IMAGES);
144         EXTENSION_MAP.put("arw", IMAGES);
145         EXTENSION_MAP.put("rgb", IMAGES);
146         EXTENSION_MAP.put("xbm", IMAGES);
147         EXTENSION_MAP.put("xpm", IMAGES);
148         EXTENSION_MAP.put("xwd", IMAGES);
149     }
150 
151     /**
152      * Returns the file categorization measurement result.
153      * @param path Directory to collect and categorize storage in.
154      */
getMeasurementResult(File path)155     public static MeasurementResult getMeasurementResult(File path) {
156         return collectFiles(StorageManager.maybeTranslateEmulatedPathToInternal(path),
157                 new MeasurementResult());
158     }
159 
160     /**
161      * Returns the file categorization result for the primary internal storage UUID.
162      *
163      * @param context
164      */
getMeasurementResult(Context context)165     public static MeasurementResult getMeasurementResult(Context context) {
166         MeasurementResult result = new MeasurementResult();
167         StorageStatsManager ssm =
168                 (StorageStatsManager) context.getSystemService(Context.STORAGE_STATS_SERVICE);
169         ExternalStorageStats stats = null;
170         try {
171             stats =
172                     ssm.queryExternalStatsForUser(
173                             StorageManager.UUID_PRIVATE_INTERNAL,
174                             UserHandle.of(context.getUserId()));
175             result.imagesSize = stats.getImageBytes();
176             result.videosSize = stats.getVideoBytes();
177             result.audioSize = stats.getAudioBytes();
178             result.miscSize =
179                     stats.getTotalBytes()
180                             - result.imagesSize
181                             - result.videosSize
182                             - result.audioSize;
183         } catch (IOException e) {
184             throw new IllegalStateException("Could not query storage");
185         }
186 
187         return result;
188     }
189 
190     /**
191      * Returns the size of a system for a given context. This is done by finding the difference
192      * between the shared data and the total primary storage size.
193      *
194      * @param context Context to use to get storage information.
195      */
getSystemSize(Context context)196     public static long getSystemSize(Context context) {
197         PackageManager pm = context.getPackageManager();
198         VolumeInfo primaryVolume = pm.getPrimaryStorageCurrentVolume();
199 
200         StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
201         VolumeInfo shared = sm.findEmulatedForPrivate(primaryVolume);
202         if (shared == null) {
203             return 0;
204         }
205 
206         // In some cases, the path may be null -- we can't determine the size in this case.
207         final File sharedPath = shared.getPath();
208         if (sharedPath == null) {
209           return 0;
210         }
211 
212         final long sharedDataSize = sharedPath.getTotalSpace();
213         long systemSize = sm.getPrimaryStorageSize() - sharedDataSize;
214 
215         // This case is not exceptional -- we just fallback to the shared data volume in this case.
216         if (systemSize <= 0) {
217             return 0;
218         }
219 
220         return systemSize;
221     }
222 
collectFiles(File file, MeasurementResult result)223     private static MeasurementResult collectFiles(File file, MeasurementResult result) {
224         File[] files = file.listFiles();
225 
226         if (files == null) {
227             return result;
228         }
229 
230         for (File f : files) {
231             if (f.isDirectory()) {
232                 try {
233                     collectFiles(f, result);
234                 } catch (StackOverflowError e) {
235                     return result;
236                 }
237             } else {
238                 handleFile(result, f);
239             }
240         }
241 
242         return result;
243     }
244 
handleFile(MeasurementResult result, File f)245     private static void handleFile(MeasurementResult result, File f) {
246         long fileSize = f.length();
247         int fileType = EXTENSION_MAP.getOrDefault(getExtensionForFile(f), UNRECOGNIZED);
248         switch (fileType) {
249             case AUDIO:
250                 result.audioSize += fileSize;
251                 break;
252             case VIDEO:
253                 result.videosSize += fileSize;
254                 break;
255             case IMAGES:
256                 result.imagesSize += fileSize;
257                 break;
258             default:
259                 result.miscSize += fileSize;
260         }
261     }
262 
getExtensionForFile(File file)263     private static String getExtensionForFile(File file) {
264         String fileName = file.getName();
265         int index = fileName.lastIndexOf('.');
266         if (index == -1) {
267             return "";
268         }
269         return fileName.substring(index + 1).toLowerCase();
270     }
271 
272     /**
273      * MeasurementResult contains a storage categorization result.
274      */
275     public static class MeasurementResult {
276         public long imagesSize;
277         public long videosSize;
278         public long miscSize;
279         public long audioSize;
280 
281         /**
282          * Sums up the storage taken by all of the categorizable sizes in the measurement.
283          */
totalAccountedSize()284         public long totalAccountedSize() {
285             return imagesSize + videosSize + miscSize + audioSize;
286         }
287     }
288 }
289