Home | History | Annotate | Download | only in storage
      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      */
    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      */
    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      */
    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 
    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 
    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 
    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          */
    284         public long totalAccountedSize() {
    285             return imagesSize + videosSize + miscSize + audioSize;
    286         }
    287     }
    288 }
    289