/*
|
* 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.content.pm.PackageStats;
|
import android.os.Environment;
|
import android.os.UserHandle;
|
import android.util.ArrayMap;
|
import android.util.Log;
|
|
import com.android.server.storage.FileCollector.MeasurementResult;
|
|
import org.json.JSONArray;
|
import org.json.JSONException;
|
import org.json.JSONObject;
|
|
import java.io.File;
|
import java.io.FileNotFoundException;
|
import java.io.PrintWriter;
|
import java.util.List;
|
import java.util.Map;
|
|
/**
|
* DiskStatsFileLogger logs collected storage information to a file in a JSON format.
|
*
|
* The following information is cached in the file:
|
* 1. Size of images on disk.
|
* 2. Size of videos on disk.
|
* 3. Size of audio on disk.
|
* 4. Size of the downloads folder.
|
* 5. System size.
|
* 6. Aggregate and individual app and app cache sizes.
|
* 7. How much storage couldn't be categorized in one of the above categories.
|
*/
|
public class DiskStatsFileLogger {
|
private static final String TAG = "DiskStatsLogger";
|
|
public static final String PHOTOS_KEY = "photosSize";
|
public static final String VIDEOS_KEY = "videosSize";
|
public static final String AUDIO_KEY = "audioSize";
|
public static final String DOWNLOADS_KEY = "downloadsSize";
|
public static final String SYSTEM_KEY = "systemSize";
|
public static final String MISC_KEY = "otherSize";
|
public static final String APP_SIZE_AGG_KEY = "appSize";
|
public static final String APP_DATA_SIZE_AGG_KEY = "appDataSize";
|
public static final String APP_CACHE_AGG_KEY = "cacheSize";
|
public static final String PACKAGE_NAMES_KEY = "packageNames";
|
public static final String APP_SIZES_KEY = "appSizes";
|
public static final String APP_CACHES_KEY = "cacheSizes";
|
public static final String APP_DATA_KEY = "appDataSizes";
|
public static final String LAST_QUERY_TIMESTAMP_KEY = "queryTime";
|
|
private MeasurementResult mResult;
|
private long mDownloadsSize;
|
private long mSystemSize;
|
private List<PackageStats> mPackageStats;
|
|
/**
|
* Constructs a DiskStatsFileLogger with calculated measurement results.
|
*/
|
public DiskStatsFileLogger(MeasurementResult result, MeasurementResult downloadsResult,
|
List<PackageStats> stats, long systemSize) {
|
mResult = result;
|
mDownloadsSize = downloadsResult.totalAccountedSize();
|
mSystemSize = systemSize;
|
mPackageStats = stats;
|
}
|
|
/**
|
* Dumps the storage collection output to a file.
|
* @param file File to write the output into.
|
* @throws FileNotFoundException
|
*/
|
public void dumpToFile(File file) throws FileNotFoundException {
|
PrintWriter pw = new PrintWriter(file);
|
JSONObject representation = getJsonRepresentation();
|
if (representation != null) {
|
pw.println(representation);
|
}
|
pw.close();
|
}
|
|
private JSONObject getJsonRepresentation() {
|
JSONObject json = new JSONObject();
|
try {
|
json.put(LAST_QUERY_TIMESTAMP_KEY, System.currentTimeMillis());
|
json.put(PHOTOS_KEY, mResult.imagesSize);
|
json.put(VIDEOS_KEY, mResult.videosSize);
|
json.put(AUDIO_KEY, mResult.audioSize);
|
json.put(DOWNLOADS_KEY, mDownloadsSize);
|
json.put(SYSTEM_KEY, mSystemSize);
|
json.put(MISC_KEY, mResult.miscSize);
|
addAppsToJson(json);
|
} catch (JSONException e) {
|
Log.e(TAG, e.toString());
|
return null;
|
}
|
|
return json;
|
}
|
|
private void addAppsToJson(JSONObject json) throws JSONException {
|
JSONArray names = new JSONArray();
|
JSONArray appSizeList = new JSONArray();
|
JSONArray appDataSizeList = new JSONArray();
|
JSONArray cacheSizeList = new JSONArray();
|
|
long appSizeSum = 0L;
|
long appDataSizeSum = 0L;
|
long cacheSizeSum = 0L;
|
boolean isExternal = Environment.isExternalStorageEmulated();
|
for (Map.Entry<String, PackageStats> entry : filterOnlyPrimaryUser().entrySet()) {
|
PackageStats stat = entry.getValue();
|
long appSize = stat.codeSize;
|
long appDataSize = stat.dataSize;
|
long cacheSize = stat.cacheSize;
|
if (isExternal) {
|
appSize += stat.externalCodeSize;
|
appDataSize += stat.externalDataSize;
|
cacheSize += stat.externalCacheSize;
|
}
|
appSizeSum += appSize;
|
appDataSizeSum += appDataSize;
|
cacheSizeSum += cacheSize;
|
|
names.put(stat.packageName);
|
appSizeList.put(appSize);
|
appDataSizeList.put(appDataSize);
|
cacheSizeList.put(cacheSize);
|
}
|
json.put(PACKAGE_NAMES_KEY, names);
|
json.put(APP_SIZES_KEY, appSizeList);
|
json.put(APP_CACHES_KEY, cacheSizeList);
|
json.put(APP_DATA_KEY, appDataSizeList);
|
json.put(APP_SIZE_AGG_KEY, appSizeSum);
|
json.put(APP_CACHE_AGG_KEY, cacheSizeSum);
|
json.put(APP_DATA_SIZE_AGG_KEY, appDataSizeSum);
|
}
|
|
/**
|
* A given package may exist for multiple users with distinct sizes. This function filters
|
* the packages that do not belong to user 0 out to ensure that we get good stats for a subset.
|
* @return A mapping of package name to merged package stats.
|
*/
|
private ArrayMap<String, PackageStats> filterOnlyPrimaryUser() {
|
ArrayMap<String, PackageStats> packageMap = new ArrayMap<>();
|
for (PackageStats stat : mPackageStats) {
|
if (stat.userHandle != UserHandle.USER_SYSTEM) {
|
continue;
|
}
|
|
PackageStats existingStats = packageMap.get(stat.packageName);
|
if (existingStats != null) {
|
existingStats.cacheSize += stat.cacheSize;
|
existingStats.codeSize += stat.codeSize;
|
existingStats.dataSize += stat.dataSize;
|
existingStats.externalCacheSize += stat.externalCacheSize;
|
existingStats.externalCodeSize += stat.externalCodeSize;
|
existingStats.externalDataSize += stat.externalDataSize;
|
} else {
|
packageMap.put(stat.packageName, new PackageStats(stat));
|
}
|
}
|
return packageMap;
|
}
|
}
|