content: /*
   * 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<String, Integer> EXTENSION_MAP = new ArrayMap<String, Integer>();
      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;
          }
      }
  }
  