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_DATA_SIZE_AGG_KEY = "appDataSize"; 60 public static final String APP_CACHE_AGG_KEY = "cacheSize"; 61 public static final String PACKAGE_NAMES_KEY = "packageNames"; 62 public static final String APP_SIZES_KEY = "appSizes"; 63 public static final String APP_CACHES_KEY = "cacheSizes"; 64 public static final String APP_DATA_KEY = "appDataSizes"; 65 public static final String LAST_QUERY_TIMESTAMP_KEY = "queryTime"; 66 67 private MeasurementResult mResult; 68 private long mDownloadsSize; 69 private long mSystemSize; 70 private List<PackageStats> mPackageStats; 71 72 /** 73 * Constructs a DiskStatsFileLogger with calculated measurement results. 74 */ DiskStatsFileLogger(MeasurementResult result, MeasurementResult downloadsResult, List<PackageStats> stats, long systemSize)75 public DiskStatsFileLogger(MeasurementResult result, MeasurementResult downloadsResult, 76 List<PackageStats> stats, long systemSize) { 77 mResult = result; 78 mDownloadsSize = downloadsResult.totalAccountedSize(); 79 mSystemSize = systemSize; 80 mPackageStats = stats; 81 } 82 83 /** 84 * Dumps the storage collection output to a file. 85 * @param file File to write the output into. 86 * @throws FileNotFoundException 87 */ dumpToFile(File file)88 public void dumpToFile(File file) throws FileNotFoundException { 89 PrintWriter pw = new PrintWriter(file); 90 JSONObject representation = getJsonRepresentation(); 91 if (representation != null) { 92 pw.println(representation); 93 } 94 pw.close(); 95 } 96 getJsonRepresentation()97 private JSONObject getJsonRepresentation() { 98 JSONObject json = new JSONObject(); 99 try { 100 json.put(LAST_QUERY_TIMESTAMP_KEY, System.currentTimeMillis()); 101 json.put(PHOTOS_KEY, mResult.imagesSize); 102 json.put(VIDEOS_KEY, mResult.videosSize); 103 json.put(AUDIO_KEY, mResult.audioSize); 104 json.put(DOWNLOADS_KEY, mDownloadsSize); 105 json.put(SYSTEM_KEY, mSystemSize); 106 json.put(MISC_KEY, mResult.miscSize); 107 addAppsToJson(json); 108 } catch (JSONException e) { 109 Log.e(TAG, e.toString()); 110 return null; 111 } 112 113 return json; 114 } 115 addAppsToJson(JSONObject json)116 private void addAppsToJson(JSONObject json) throws JSONException { 117 JSONArray names = new JSONArray(); 118 JSONArray appSizeList = new JSONArray(); 119 JSONArray appDataSizeList = new JSONArray(); 120 JSONArray cacheSizeList = new JSONArray(); 121 122 long appSizeSum = 0L; 123 long appDataSizeSum = 0L; 124 long cacheSizeSum = 0L; 125 boolean isExternal = Environment.isExternalStorageEmulated(); 126 for (Map.Entry<String, PackageStats> entry : filterOnlyPrimaryUser().entrySet()) { 127 PackageStats stat = entry.getValue(); 128 long appSize = stat.codeSize; 129 long appDataSize = stat.dataSize; 130 long cacheSize = stat.cacheSize; 131 if (isExternal) { 132 appSize += stat.externalCodeSize; 133 appDataSize += stat.externalDataSize; 134 cacheSize += stat.externalCacheSize; 135 } 136 appSizeSum += appSize; 137 appDataSizeSum += appDataSize; 138 cacheSizeSum += cacheSize; 139 140 names.put(stat.packageName); 141 appSizeList.put(appSize); 142 appDataSizeList.put(appDataSize); 143 cacheSizeList.put(cacheSize); 144 } 145 json.put(PACKAGE_NAMES_KEY, names); 146 json.put(APP_SIZES_KEY, appSizeList); 147 json.put(APP_CACHES_KEY, cacheSizeList); 148 json.put(APP_DATA_KEY, appDataSizeList); 149 json.put(APP_SIZE_AGG_KEY, appSizeSum); 150 json.put(APP_CACHE_AGG_KEY, cacheSizeSum); 151 json.put(APP_DATA_SIZE_AGG_KEY, appDataSizeSum); 152 } 153 154 /** 155 * A given package may exist for multiple users with distinct sizes. This function filters 156 * the packages that do not belong to user 0 out to ensure that we get good stats for a subset. 157 * @return A mapping of package name to merged package stats. 158 */ filterOnlyPrimaryUser()159 private ArrayMap<String, PackageStats> filterOnlyPrimaryUser() { 160 ArrayMap<String, PackageStats> packageMap = new ArrayMap<>(); 161 for (PackageStats stat : mPackageStats) { 162 if (stat.userHandle != UserHandle.USER_SYSTEM) { 163 continue; 164 } 165 166 PackageStats existingStats = packageMap.get(stat.packageName); 167 if (existingStats != null) { 168 existingStats.cacheSize += stat.cacheSize; 169 existingStats.codeSize += stat.codeSize; 170 existingStats.dataSize += stat.dataSize; 171 existingStats.externalCacheSize += stat.externalCacheSize; 172 existingStats.externalCodeSize += stat.externalCodeSize; 173 existingStats.externalDataSize += stat.externalDataSize; 174 } else { 175 packageMap.put(stat.packageName, new PackageStats(stat)); 176 } 177 } 178 return packageMap; 179 } 180 }