13c7febdbd86070337810d1ff741d35430707726aDaniel Nishi/*
23c7febdbd86070337810d1ff741d35430707726aDaniel Nishi * Copyright (C) 2016 The Android Open Source Project
33c7febdbd86070337810d1ff741d35430707726aDaniel Nishi *
43c7febdbd86070337810d1ff741d35430707726aDaniel Nishi * Licensed under the Apache License, Version 2.0 (the "License"); you may not
53c7febdbd86070337810d1ff741d35430707726aDaniel Nishi * use this file except in compliance with the License. You may obtain a copy of
63c7febdbd86070337810d1ff741d35430707726aDaniel Nishi * the License at
73c7febdbd86070337810d1ff741d35430707726aDaniel Nishi *
83c7febdbd86070337810d1ff741d35430707726aDaniel Nishi * http://www.apache.org/licenses/LICENSE2.0
93c7febdbd86070337810d1ff741d35430707726aDaniel Nishi *
103c7febdbd86070337810d1ff741d35430707726aDaniel Nishi * Unless required by applicable law or agreed to in writing, software
113c7febdbd86070337810d1ff741d35430707726aDaniel Nishi * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
123c7febdbd86070337810d1ff741d35430707726aDaniel Nishi * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
133c7febdbd86070337810d1ff741d35430707726aDaniel Nishi * License for the specific language governing permissions and limitations under
143c7febdbd86070337810d1ff741d35430707726aDaniel Nishi * the License.
153c7febdbd86070337810d1ff741d35430707726aDaniel Nishi */
163c7febdbd86070337810d1ff741d35430707726aDaniel Nishi
173c7febdbd86070337810d1ff741d35430707726aDaniel Nishipackage com.android.server.storage;
183c7febdbd86070337810d1ff741d35430707726aDaniel Nishi
193c7febdbd86070337810d1ff741d35430707726aDaniel Nishiimport android.annotation.IntDef;
205d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishiimport android.app.usage.ExternalStorageStats;
215d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishiimport android.app.usage.StorageStatsManager;
2277a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishiimport android.content.Context;
2377a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishiimport android.content.pm.PackageManager;
245d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishiimport android.os.UserHandle;
253c7febdbd86070337810d1ff741d35430707726aDaniel Nishiimport android.os.storage.StorageManager;
2677a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishiimport android.os.storage.VolumeInfo;
273c7febdbd86070337810d1ff741d35430707726aDaniel Nishiimport android.util.ArrayMap;
283c7febdbd86070337810d1ff741d35430707726aDaniel Nishi
293c7febdbd86070337810d1ff741d35430707726aDaniel Nishiimport java.io.File;
305d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishiimport java.io.IOException;
313c7febdbd86070337810d1ff741d35430707726aDaniel Nishiimport java.lang.annotation.Retention;
323c7febdbd86070337810d1ff741d35430707726aDaniel Nishiimport java.lang.annotation.RetentionPolicy;
331009c08f8e11bb050ce395e8f6a0ed318b1902a1Jeff Sharkeyimport java.util.Map;
343c7febdbd86070337810d1ff741d35430707726aDaniel Nishi
353c7febdbd86070337810d1ff741d35430707726aDaniel Nishi/**
363c7febdbd86070337810d1ff741d35430707726aDaniel Nishi * FileCollector walks over a directory and categorizes storage usage by their type.
373c7febdbd86070337810d1ff741d35430707726aDaniel Nishi */
383c7febdbd86070337810d1ff741d35430707726aDaniel Nishipublic class FileCollector {
393c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    private static final int UNRECOGNIZED = -1;
403c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    private static final int IMAGES = 0;
413c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    private static final int VIDEO = 1;
423c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    private static final int AUDIO = 2;
433c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    @Retention(RetentionPolicy.SOURCE)
443c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    @IntDef({
453c7febdbd86070337810d1ff741d35430707726aDaniel Nishi            UNRECOGNIZED,
463c7febdbd86070337810d1ff741d35430707726aDaniel Nishi            IMAGES,
473c7febdbd86070337810d1ff741d35430707726aDaniel Nishi            VIDEO,
483c7febdbd86070337810d1ff741d35430707726aDaniel Nishi            AUDIO })
493c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    private @interface FileTypes {}
503c7febdbd86070337810d1ff741d35430707726aDaniel Nishi
511009c08f8e11bb050ce395e8f6a0ed318b1902a1Jeff Sharkey
521009c08f8e11bb050ce395e8f6a0ed318b1902a1Jeff Sharkey    private static final Map<String, Integer> EXTENSION_MAP = new ArrayMap<String, Integer>();
533c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    static {
543c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        // Audio
553c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("aac", AUDIO);
563c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("amr", AUDIO);
573c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("awb", AUDIO);
583c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("snd", AUDIO);
593c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("flac", AUDIO);
603c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("mp3", AUDIO);
613c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("mpga", AUDIO);
623c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("mpega", AUDIO);
633c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("mp2", AUDIO);
643c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("m4a", AUDIO);
653c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("aif", AUDIO);
663c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("aiff", AUDIO);
673c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("aifc", AUDIO);
683c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("gsm", AUDIO);
693c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("mka", AUDIO);
703c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("m3u", AUDIO);
713c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("wma", AUDIO);
723c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("wax", AUDIO);
733c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("ra", AUDIO);
743c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("rm", AUDIO);
753c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("ram", AUDIO);
763c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("pls", AUDIO);
773c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("sd2", AUDIO);
783c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("wav", AUDIO);
793c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("ogg", AUDIO);
803c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("oga", AUDIO);
813c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        // Video
823c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("3gpp", VIDEO);
833c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("3gp", VIDEO);
843c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("3gpp2", VIDEO);
853c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("3g2", VIDEO);
863c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("avi", VIDEO);
873c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("dl", VIDEO);
883c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("dif", VIDEO);
893c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("dv", VIDEO);
903c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("fli", VIDEO);
913c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("m4v", VIDEO);
923c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("ts", VIDEO);
933c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("mpeg", VIDEO);
943c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("mpg", VIDEO);
953c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("mpe", VIDEO);
963c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("mp4", VIDEO);
973c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("vob", VIDEO);
983c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("qt", VIDEO);
993c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("mov", VIDEO);
1003c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("mxu", VIDEO);
1013c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("webm", VIDEO);
1023c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("lsf", VIDEO);
1033c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("lsx", VIDEO);
1043c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("mkv", VIDEO);
1053c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("mng", VIDEO);
1063c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("asf", VIDEO);
1073c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("asx", VIDEO);
1083c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("wm", VIDEO);
1093c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("wmv", VIDEO);
1103c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("wmx", VIDEO);
1113c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("wvx", VIDEO);
1123c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("movie", VIDEO);
1133c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("wrf", VIDEO);
1143c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        // Images
1153c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("bmp", IMAGES);
1163c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("gif", IMAGES);
1173c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("jpg", IMAGES);
1183c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("jpeg", IMAGES);
1193c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("jpe", IMAGES);
1203c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("pcx", IMAGES);
1213c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("png", IMAGES);
1223c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("svg", IMAGES);
1233c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("svgz", IMAGES);
1243c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("tiff", IMAGES);
1253c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("tif", IMAGES);
1263c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("wbmp", IMAGES);
1273c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("webp", IMAGES);
1283c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("dng", IMAGES);
1293c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("cr2", IMAGES);
1303c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("ras", IMAGES);
1313c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("art", IMAGES);
1323c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("jng", IMAGES);
1333c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("nef", IMAGES);
1343c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("nrw", IMAGES);
1353c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("orf", IMAGES);
1363c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("rw2", IMAGES);
1373c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("pef", IMAGES);
1383c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("psd", IMAGES);
1393c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("pnm", IMAGES);
1403c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("pbm", IMAGES);
1413c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("pgm", IMAGES);
1423c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("ppm", IMAGES);
1433c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("srw", IMAGES);
1443c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("arw", IMAGES);
1453c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("rgb", IMAGES);
1463c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("xbm", IMAGES);
1473c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("xpm", IMAGES);
1483c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        EXTENSION_MAP.put("xwd", IMAGES);
1493c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    }
1503c7febdbd86070337810d1ff741d35430707726aDaniel Nishi
1513c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    /**
1523c7febdbd86070337810d1ff741d35430707726aDaniel Nishi     * Returns the file categorization measurement result.
1533c7febdbd86070337810d1ff741d35430707726aDaniel Nishi     * @param path Directory to collect and categorize storage in.
1543c7febdbd86070337810d1ff741d35430707726aDaniel Nishi     */
1553c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    public static MeasurementResult getMeasurementResult(File path) {
1563c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        return collectFiles(StorageManager.maybeTranslateEmulatedPathToInternal(path),
1573c7febdbd86070337810d1ff741d35430707726aDaniel Nishi                new MeasurementResult());
1583c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    }
1593c7febdbd86070337810d1ff741d35430707726aDaniel Nishi
16077a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    /**
1615d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi     * Returns the file categorization result for the primary internal storage UUID.
1625d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi     *
1635d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi     * @param context
1645d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi     */
1655d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi    public static MeasurementResult getMeasurementResult(Context context) {
1665d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi        MeasurementResult result = new MeasurementResult();
1675d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi        StorageStatsManager ssm =
1685d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi                (StorageStatsManager) context.getSystemService(Context.STORAGE_STATS_SERVICE);
1695d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi        ExternalStorageStats stats = null;
1705d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi        try {
1715d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi            stats =
1725d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi                    ssm.queryExternalStatsForUser(
1735d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi                            StorageManager.UUID_PRIVATE_INTERNAL,
1745d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi                            UserHandle.of(context.getUserId()));
1755d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi            result.imagesSize = stats.getImageBytes();
1765d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi            result.videosSize = stats.getVideoBytes();
1775d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi            result.audioSize = stats.getAudioBytes();
1785d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi            result.miscSize =
1795d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi                    stats.getTotalBytes()
1805d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi                            - result.imagesSize
1815d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi                            - result.videosSize
1825d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi                            - result.audioSize;
1835d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi        } catch (IOException e) {
1845d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi            throw new IllegalStateException("Could not query storage");
1855d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi        }
1865d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi
1875d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi        return result;
1885d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi    }
1895d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi
1905d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi    /**
19177a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi     * Returns the size of a system for a given context. This is done by finding the difference
19277a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi     * between the shared data and the total primary storage size.
1935d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi     *
19477a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi     * @param context Context to use to get storage information.
19577a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi     */
19677a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    public static long getSystemSize(Context context) {
19777a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        PackageManager pm = context.getPackageManager();
19877a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        VolumeInfo primaryVolume = pm.getPrimaryStorageCurrentVolume();
19977a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
2005d230dc202cb03185652e12b3024ceea1132dd4dDaniel Nishi        StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
20177a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        VolumeInfo shared = sm.findEmulatedForPrivate(primaryVolume);
20277a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        if (shared == null) {
20377a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            return 0;
20477a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        }
20577a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
206e4bea2d769ae9ab3df028886d61e1215e43fab6fDaniel Nishi        // In some cases, the path may be null -- we can't determine the size in this case.
207e4bea2d769ae9ab3df028886d61e1215e43fab6fDaniel Nishi        final File sharedPath = shared.getPath();
208e4bea2d769ae9ab3df028886d61e1215e43fab6fDaniel Nishi        if (sharedPath == null) {
209e4bea2d769ae9ab3df028886d61e1215e43fab6fDaniel Nishi          return 0;
210e4bea2d769ae9ab3df028886d61e1215e43fab6fDaniel Nishi        }
211e4bea2d769ae9ab3df028886d61e1215e43fab6fDaniel Nishi
212e4bea2d769ae9ab3df028886d61e1215e43fab6fDaniel Nishi        final long sharedDataSize = sharedPath.getTotalSpace();
21377a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        long systemSize = sm.getPrimaryStorageSize() - sharedDataSize;
21477a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
21577a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        // This case is not exceptional -- we just fallback to the shared data volume in this case.
21677a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        if (systemSize <= 0) {
21777a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            return 0;
21877a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        }
21977a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
22077a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        return systemSize;
22177a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    }
22277a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
2233c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    private static MeasurementResult collectFiles(File file, MeasurementResult result) {
2243c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        File[] files = file.listFiles();
2253c7febdbd86070337810d1ff741d35430707726aDaniel Nishi
2263c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        if (files == null) {
2273c7febdbd86070337810d1ff741d35430707726aDaniel Nishi            return result;
2283c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        }
2293c7febdbd86070337810d1ff741d35430707726aDaniel Nishi
2303c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        for (File f : files) {
2313c7febdbd86070337810d1ff741d35430707726aDaniel Nishi            if (f.isDirectory()) {
2323c7febdbd86070337810d1ff741d35430707726aDaniel Nishi                try {
2333c7febdbd86070337810d1ff741d35430707726aDaniel Nishi                    collectFiles(f, result);
2343c7febdbd86070337810d1ff741d35430707726aDaniel Nishi                } catch (StackOverflowError e) {
2353c7febdbd86070337810d1ff741d35430707726aDaniel Nishi                    return result;
2363c7febdbd86070337810d1ff741d35430707726aDaniel Nishi                }
2373c7febdbd86070337810d1ff741d35430707726aDaniel Nishi            } else {
2383c7febdbd86070337810d1ff741d35430707726aDaniel Nishi                handleFile(result, f);
2393c7febdbd86070337810d1ff741d35430707726aDaniel Nishi            }
2403c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        }
2413c7febdbd86070337810d1ff741d35430707726aDaniel Nishi
2423c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        return result;
2433c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    }
2443c7febdbd86070337810d1ff741d35430707726aDaniel Nishi
2453c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    private static void handleFile(MeasurementResult result, File f) {
2463c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        long fileSize = f.length();
2473c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        int fileType = EXTENSION_MAP.getOrDefault(getExtensionForFile(f), UNRECOGNIZED);
2483c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        switch (fileType) {
2493c7febdbd86070337810d1ff741d35430707726aDaniel Nishi            case AUDIO:
2503c7febdbd86070337810d1ff741d35430707726aDaniel Nishi                result.audioSize += fileSize;
2513c7febdbd86070337810d1ff741d35430707726aDaniel Nishi                break;
2523c7febdbd86070337810d1ff741d35430707726aDaniel Nishi            case VIDEO:
2533c7febdbd86070337810d1ff741d35430707726aDaniel Nishi                result.videosSize += fileSize;
2543c7febdbd86070337810d1ff741d35430707726aDaniel Nishi                break;
2553c7febdbd86070337810d1ff741d35430707726aDaniel Nishi            case IMAGES:
2563c7febdbd86070337810d1ff741d35430707726aDaniel Nishi                result.imagesSize += fileSize;
2573c7febdbd86070337810d1ff741d35430707726aDaniel Nishi                break;
2583c7febdbd86070337810d1ff741d35430707726aDaniel Nishi            default:
2593c7febdbd86070337810d1ff741d35430707726aDaniel Nishi                result.miscSize += fileSize;
2603c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        }
2613c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    }
2623c7febdbd86070337810d1ff741d35430707726aDaniel Nishi
2633c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    private static String getExtensionForFile(File file) {
2643c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        String fileName = file.getName();
2653c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        int index = fileName.lastIndexOf('.');
2663c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        if (index == -1) {
2673c7febdbd86070337810d1ff741d35430707726aDaniel Nishi            return "";
2683c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        }
2693c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        return fileName.substring(index + 1).toLowerCase();
2703c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    }
2713c7febdbd86070337810d1ff741d35430707726aDaniel Nishi
2723c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    /**
2733c7febdbd86070337810d1ff741d35430707726aDaniel Nishi     * MeasurementResult contains a storage categorization result.
2743c7febdbd86070337810d1ff741d35430707726aDaniel Nishi     */
2753c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    public static class MeasurementResult {
2763c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        public long imagesSize;
2773c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        public long videosSize;
2783c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        public long miscSize;
2793c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        public long audioSize;
2803c7febdbd86070337810d1ff741d35430707726aDaniel Nishi
2813c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        /**
2823c7febdbd86070337810d1ff741d35430707726aDaniel Nishi         * Sums up the storage taken by all of the categorizable sizes in the measurement.
2833c7febdbd86070337810d1ff741d35430707726aDaniel Nishi         */
2843c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        public long totalAccountedSize() {
2853c7febdbd86070337810d1ff741d35430707726aDaniel Nishi            return imagesSize + videosSize + miscSize + audioSize;
2863c7febdbd86070337810d1ff741d35430707726aDaniel Nishi        }
2873c7febdbd86070337810d1ff741d35430707726aDaniel Nishi    }
2883c7febdbd86070337810d1ff741d35430707726aDaniel Nishi}
289