/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.android.server.storage; import android.annotation.IntDef; import android.app.usage.ExternalStorageStats; import android.app.usage.StorageStatsManager; import android.content.Context; import android.content.pm.PackageManager; import android.os.UserHandle; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.util.ArrayMap; import java.io.File; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Map; /** * FileCollector walks over a directory and categorizes storage usage by their type. */ public class FileCollector { private static final int UNRECOGNIZED = -1; private static final int IMAGES = 0; private static final int VIDEO = 1; private static final int AUDIO = 2; @Retention(RetentionPolicy.SOURCE) @IntDef({ UNRECOGNIZED, IMAGES, VIDEO, AUDIO }) private @interface FileTypes {} private static final Map EXTENSION_MAP = new ArrayMap(); static { // Audio EXTENSION_MAP.put("aac", AUDIO); EXTENSION_MAP.put("amr", AUDIO); EXTENSION_MAP.put("awb", AUDIO); EXTENSION_MAP.put("snd", AUDIO); EXTENSION_MAP.put("flac", AUDIO); EXTENSION_MAP.put("mp3", AUDIO); EXTENSION_MAP.put("mpga", AUDIO); EXTENSION_MAP.put("mpega", AUDIO); EXTENSION_MAP.put("mp2", AUDIO); EXTENSION_MAP.put("m4a", AUDIO); EXTENSION_MAP.put("aif", AUDIO); EXTENSION_MAP.put("aiff", AUDIO); EXTENSION_MAP.put("aifc", AUDIO); EXTENSION_MAP.put("gsm", AUDIO); EXTENSION_MAP.put("mka", AUDIO); EXTENSION_MAP.put("m3u", AUDIO); EXTENSION_MAP.put("wma", AUDIO); EXTENSION_MAP.put("wax", AUDIO); EXTENSION_MAP.put("ra", AUDIO); EXTENSION_MAP.put("rm", AUDIO); EXTENSION_MAP.put("ram", AUDIO); EXTENSION_MAP.put("pls", AUDIO); EXTENSION_MAP.put("sd2", AUDIO); EXTENSION_MAP.put("wav", AUDIO); EXTENSION_MAP.put("ogg", AUDIO); EXTENSION_MAP.put("oga", AUDIO); // Video EXTENSION_MAP.put("3gpp", VIDEO); EXTENSION_MAP.put("3gp", VIDEO); EXTENSION_MAP.put("3gpp2", VIDEO); EXTENSION_MAP.put("3g2", VIDEO); EXTENSION_MAP.put("avi", VIDEO); EXTENSION_MAP.put("dl", VIDEO); EXTENSION_MAP.put("dif", VIDEO); EXTENSION_MAP.put("dv", VIDEO); EXTENSION_MAP.put("fli", VIDEO); EXTENSION_MAP.put("m4v", VIDEO); EXTENSION_MAP.put("ts", VIDEO); EXTENSION_MAP.put("mpeg", VIDEO); EXTENSION_MAP.put("mpg", VIDEO); EXTENSION_MAP.put("mpe", VIDEO); EXTENSION_MAP.put("mp4", VIDEO); EXTENSION_MAP.put("vob", VIDEO); EXTENSION_MAP.put("qt", VIDEO); EXTENSION_MAP.put("mov", VIDEO); EXTENSION_MAP.put("mxu", VIDEO); EXTENSION_MAP.put("webm", VIDEO); EXTENSION_MAP.put("lsf", VIDEO); EXTENSION_MAP.put("lsx", VIDEO); EXTENSION_MAP.put("mkv", VIDEO); EXTENSION_MAP.put("mng", VIDEO); EXTENSION_MAP.put("asf", VIDEO); EXTENSION_MAP.put("asx", VIDEO); EXTENSION_MAP.put("wm", VIDEO); EXTENSION_MAP.put("wmv", VIDEO); EXTENSION_MAP.put("wmx", VIDEO); EXTENSION_MAP.put("wvx", VIDEO); EXTENSION_MAP.put("movie", VIDEO); EXTENSION_MAP.put("wrf", VIDEO); // Images EXTENSION_MAP.put("bmp", IMAGES); EXTENSION_MAP.put("gif", IMAGES); EXTENSION_MAP.put("jpg", IMAGES); EXTENSION_MAP.put("jpeg", IMAGES); EXTENSION_MAP.put("jpe", IMAGES); EXTENSION_MAP.put("pcx", IMAGES); EXTENSION_MAP.put("png", IMAGES); EXTENSION_MAP.put("svg", IMAGES); EXTENSION_MAP.put("svgz", IMAGES); EXTENSION_MAP.put("tiff", IMAGES); EXTENSION_MAP.put("tif", IMAGES); EXTENSION_MAP.put("wbmp", IMAGES); EXTENSION_MAP.put("webp", IMAGES); EXTENSION_MAP.put("dng", IMAGES); EXTENSION_MAP.put("cr2", IMAGES); EXTENSION_MAP.put("ras", IMAGES); EXTENSION_MAP.put("art", IMAGES); EXTENSION_MAP.put("jng", IMAGES); EXTENSION_MAP.put("nef", IMAGES); EXTENSION_MAP.put("nrw", IMAGES); EXTENSION_MAP.put("orf", IMAGES); EXTENSION_MAP.put("rw2", IMAGES); EXTENSION_MAP.put("pef", IMAGES); EXTENSION_MAP.put("psd", IMAGES); EXTENSION_MAP.put("pnm", IMAGES); EXTENSION_MAP.put("pbm", IMAGES); EXTENSION_MAP.put("pgm", IMAGES); EXTENSION_MAP.put("ppm", IMAGES); EXTENSION_MAP.put("srw", IMAGES); EXTENSION_MAP.put("arw", IMAGES); EXTENSION_MAP.put("rgb", IMAGES); EXTENSION_MAP.put("xbm", IMAGES); EXTENSION_MAP.put("xpm", IMAGES); EXTENSION_MAP.put("xwd", IMAGES); } /** * Returns the file categorization measurement result. * @param path Directory to collect and categorize storage in. */ public static MeasurementResult getMeasurementResult(File path) { return collectFiles(StorageManager.maybeTranslateEmulatedPathToInternal(path), new MeasurementResult()); } /** * Returns the file categorization result for the primary internal storage UUID. * * @param context */ public static MeasurementResult getMeasurementResult(Context context) { MeasurementResult result = new MeasurementResult(); StorageStatsManager ssm = (StorageStatsManager) context.getSystemService(Context.STORAGE_STATS_SERVICE); ExternalStorageStats stats = null; try { stats = ssm.queryExternalStatsForUser( StorageManager.UUID_PRIVATE_INTERNAL, UserHandle.of(context.getUserId())); result.imagesSize = stats.getImageBytes(); result.videosSize = stats.getVideoBytes(); result.audioSize = stats.getAudioBytes(); result.miscSize = stats.getTotalBytes() - result.imagesSize - result.videosSize - result.audioSize; } catch (IOException e) { throw new IllegalStateException("Could not query storage"); } return result; } /** * Returns the size of a system for a given context. This is done by finding the difference * between the shared data and the total primary storage size. * * @param context Context to use to get storage information. */ public static long getSystemSize(Context context) { PackageManager pm = context.getPackageManager(); VolumeInfo primaryVolume = pm.getPrimaryStorageCurrentVolume(); StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE); VolumeInfo shared = sm.findEmulatedForPrivate(primaryVolume); if (shared == null) { return 0; } // In some cases, the path may be null -- we can't determine the size in this case. final File sharedPath = shared.getPath(); if (sharedPath == null) { return 0; } final long sharedDataSize = sharedPath.getTotalSpace(); long systemSize = sm.getPrimaryStorageSize() - sharedDataSize; // This case is not exceptional -- we just fallback to the shared data volume in this case. if (systemSize <= 0) { return 0; } return systemSize; } private static MeasurementResult collectFiles(File file, MeasurementResult result) { File[] files = file.listFiles(); if (files == null) { return result; } for (File f : files) { if (f.isDirectory()) { try { collectFiles(f, result); } catch (StackOverflowError e) { return result; } } else { handleFile(result, f); } } return result; } private static void handleFile(MeasurementResult result, File f) { long fileSize = f.length(); int fileType = EXTENSION_MAP.getOrDefault(getExtensionForFile(f), UNRECOGNIZED); switch (fileType) { case AUDIO: result.audioSize += fileSize; break; case VIDEO: result.videosSize += fileSize; break; case IMAGES: result.imagesSize += fileSize; break; default: result.miscSize += fileSize; } } private static String getExtensionForFile(File file) { String fileName = file.getName(); int index = fileName.lastIndexOf('.'); if (index == -1) { return ""; } return fileName.substring(index + 1).toLowerCase(); } /** * MeasurementResult contains a storage categorization result. */ public static class MeasurementResult { public long imagesSize; public long videosSize; public long miscSize; public long audioSize; /** * Sums up the storage taken by all of the categorizable sizes in the measurement. */ public long totalAccountedSize() { return imagesSize + videosSize + miscSize + audioSize; } } }