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.content.pm.PackageStats;
     20 import android.os.Environment;
     21 import android.os.UserHandle;
     22 import android.util.ArrayMap;
     23 import android.util.Log;
     24 
     25 import com.android.server.storage.FileCollector.MeasurementResult;
     26 
     27 import org.json.JSONArray;
     28 import org.json.JSONException;
     29 import org.json.JSONObject;
     30 
     31 import java.io.File;
     32 import java.io.FileNotFoundException;
     33 import java.io.PrintWriter;
     34 import java.util.List;
     35 import java.util.Map;
     36 
     37 /**
     38  * DiskStatsFileLogger logs collected storage information to a file in a JSON format.
     39  *
     40  * The following information is cached in the file:
     41  * 1. Size of images on disk.
     42  * 2. Size of videos on disk.
     43  * 3. Size of audio on disk.
     44  * 4. Size of the downloads folder.
     45  * 5. System size.
     46  * 6. Aggregate and individual app and app cache sizes.
     47  * 7. How much storage couldn't be categorized in one of the above categories.
     48  */
     49 public class DiskStatsFileLogger {
     50     private static final String TAG = "DiskStatsLogger";
     51 
     52     public static final String PHOTOS_KEY = "photosSize";
     53     public static final String VIDEOS_KEY = "videosSize";
     54     public static final String AUDIO_KEY = "audioSize";
     55     public static final String DOWNLOADS_KEY = "downloadsSize";
     56     public static final String SYSTEM_KEY = "systemSize";
     57     public static final String MISC_KEY = "otherSize";
     58     public static final String APP_SIZE_AGG_KEY = "appSize";
     59     public static final String APP_CACHE_AGG_KEY = "cacheSize";
     60     public static final String PACKAGE_NAMES_KEY = "packageNames";
     61     public static final String APP_SIZES_KEY = "appSizes";
     62     public static final String APP_CACHES_KEY = "cacheSizes";
     63     public static final String LAST_QUERY_TIMESTAMP_KEY = "queryTime";
     64 
     65     private MeasurementResult mResult;
     66     private long mDownloadsSize;
     67     private long mSystemSize;
     68     private List<PackageStats> mPackageStats;
     69 
     70     /**
     71      * Constructs a DiskStatsFileLogger with calculated measurement results.
     72      */
     73     public DiskStatsFileLogger(MeasurementResult result, MeasurementResult downloadsResult,
     74             List<PackageStats> stats, long systemSize) {
     75         mResult = result;
     76         mDownloadsSize = downloadsResult.totalAccountedSize();
     77         mSystemSize = systemSize;
     78         mPackageStats = stats;
     79     }
     80 
     81     /**
     82      * Dumps the storage collection output to a file.
     83      * @param file File to write the output into.
     84      * @throws FileNotFoundException
     85      */
     86     public void dumpToFile(File file) throws FileNotFoundException {
     87         PrintWriter pw = new PrintWriter(file);
     88         JSONObject representation = getJsonRepresentation();
     89         if (representation != null) {
     90             pw.println(representation);
     91         }
     92         pw.close();
     93     }
     94 
     95     private JSONObject getJsonRepresentation() {
     96         JSONObject json = new JSONObject();
     97         try {
     98             json.put(LAST_QUERY_TIMESTAMP_KEY, System.currentTimeMillis());
     99             json.put(PHOTOS_KEY, mResult.imagesSize);
    100             json.put(VIDEOS_KEY, mResult.videosSize);
    101             json.put(AUDIO_KEY, mResult.audioSize);
    102             json.put(DOWNLOADS_KEY, mDownloadsSize);
    103             json.put(SYSTEM_KEY, mSystemSize);
    104             json.put(MISC_KEY, mResult.miscSize);
    105             addAppsToJson(json);
    106         } catch (JSONException e) {
    107             Log.e(TAG, e.toString());
    108             return null;
    109         }
    110 
    111         return json;
    112     }
    113 
    114     private void addAppsToJson(JSONObject json) throws JSONException {
    115         JSONArray names = new JSONArray();
    116         JSONArray appSizeList = new JSONArray();
    117         JSONArray cacheSizeList = new JSONArray();
    118 
    119         long appSizeSum = 0L;
    120         long cacheSizeSum = 0L;
    121         boolean isExternal = Environment.isExternalStorageEmulated();
    122         for (Map.Entry<String, PackageStats> entry : filterOnlyPrimaryUser().entrySet()) {
    123             PackageStats stat = entry.getValue();
    124             long appSize = stat.codeSize + stat.dataSize;
    125             long cacheSize = stat.cacheSize;
    126             if (isExternal) {
    127                 appSize += stat.externalCodeSize + stat.externalDataSize;
    128                 cacheSize += stat.externalCacheSize;
    129             }
    130             appSizeSum += appSize;
    131             cacheSizeSum += cacheSize;
    132 
    133             names.put(stat.packageName);
    134             appSizeList.put(appSize);
    135             cacheSizeList.put(cacheSize);
    136         }
    137         json.put(PACKAGE_NAMES_KEY, names);
    138         json.put(APP_SIZES_KEY, appSizeList);
    139         json.put(APP_CACHES_KEY, cacheSizeList);
    140         json.put(APP_SIZE_AGG_KEY, appSizeSum);
    141         json.put(APP_CACHE_AGG_KEY, cacheSizeSum);
    142     }
    143 
    144     /**
    145      * A given package may exist for multiple users with distinct sizes. This function filters
    146      * the packages that do not belong to user 0 out to ensure that we get good stats for a subset.
    147      * @return A mapping of package name to merged package stats.
    148      */
    149     private ArrayMap<String, PackageStats> filterOnlyPrimaryUser() {
    150         ArrayMap<String, PackageStats> packageMap = new ArrayMap<>();
    151         for (PackageStats stat : mPackageStats) {
    152             if (stat.userHandle != UserHandle.USER_SYSTEM) {
    153                 continue;
    154             }
    155 
    156             PackageStats existingStats = packageMap.get(stat.packageName);
    157             if (existingStats != null) {
    158                 existingStats.cacheSize += stat.cacheSize;
    159                 existingStats.codeSize += stat.codeSize;
    160                 existingStats.dataSize += stat.dataSize;
    161                 existingStats.externalCacheSize += stat.externalCacheSize;
    162                 existingStats.externalCodeSize += stat.externalCodeSize;
    163                 existingStats.externalDataSize += stat.externalDataSize;
    164             } else {
    165                 packageMap.put(stat.packageName, new PackageStats(stat));
    166             }
    167         }
    168         return packageMap;
    169     }
    170 }