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 }