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