177a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi/*
277a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi * Copyright (C) 2016 The Android Open Source Project
377a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi *
477a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi * Licensed under the Apache License, Version 2.0 (the "License"); you may not
577a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi * use this file except in compliance with the License. You may obtain a copy of
677a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi * the License at
777a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi *
877a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi * http://www.apache.org/licenses/LICENSE2.0
977a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi *
1077a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi * Unless required by applicable law or agreed to in writing, software
1177a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1277a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1377a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi * License for the specific language governing permissions and limitations under
1477a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi * the License.
1577a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi */
1677a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
1777a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishipackage com.android.server.storage;
1877a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
1977a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishiimport android.content.pm.PackageStats;
2077a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishiimport android.os.Environment;
21e47eac74f7c80738017734342f66b615189e7596Daniel Nishiimport android.os.UserHandle;
2277a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishiimport android.util.ArrayMap;
2377a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishiimport android.util.Log;
2477a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
2577a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishiimport com.android.server.storage.FileCollector.MeasurementResult;
2677a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
2777a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishiimport org.json.JSONArray;
2877a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishiimport org.json.JSONException;
2977a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishiimport org.json.JSONObject;
3077a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
3177a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishiimport java.io.File;
3277a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishiimport java.io.FileNotFoundException;
3377a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishiimport java.io.PrintWriter;
3477a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishiimport java.util.List;
3577a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishiimport java.util.Map;
3677a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
3777a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi/**
3877a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi * DiskStatsFileLogger logs collected storage information to a file in a JSON format.
3977a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi *
4077a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi * The following information is cached in the file:
4177a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi * 1. Size of images on disk.
4277a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi * 2. Size of videos on disk.
4377a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi * 3. Size of audio on disk.
4477a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi * 4. Size of the downloads folder.
4577a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi * 5. System size.
4677a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi * 6. Aggregate and individual app and app cache sizes.
4777a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi * 7. How much storage couldn't be categorized in one of the above categories.
4877a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi */
4977a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishipublic class DiskStatsFileLogger {
5077a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    private static final String TAG = "DiskStatsLogger";
5177a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
5277a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    public static final String PHOTOS_KEY = "photosSize";
5377a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    public static final String VIDEOS_KEY = "videosSize";
5477a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    public static final String AUDIO_KEY = "audioSize";
5577a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    public static final String DOWNLOADS_KEY = "downloadsSize";
5677a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    public static final String SYSTEM_KEY = "systemSize";
5777a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    public static final String MISC_KEY = "otherSize";
5877a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    public static final String APP_SIZE_AGG_KEY = "appSize";
5977a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    public static final String APP_CACHE_AGG_KEY = "cacheSize";
6077a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    public static final String PACKAGE_NAMES_KEY = "packageNames";
6177a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    public static final String APP_SIZES_KEY = "appSizes";
6277a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    public static final String APP_CACHES_KEY = "cacheSizes";
6377a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    public static final String LAST_QUERY_TIMESTAMP_KEY = "queryTime";
6477a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
6577a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    private MeasurementResult mResult;
6677a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    private long mDownloadsSize;
6777a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    private long mSystemSize;
6877a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    private List<PackageStats> mPackageStats;
6977a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
7077a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    /**
7177a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi     * Constructs a DiskStatsFileLogger with calculated measurement results.
7277a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi     */
7377a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    public DiskStatsFileLogger(MeasurementResult result, MeasurementResult downloadsResult,
7477a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            List<PackageStats> stats, long systemSize) {
7577a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        mResult = result;
7677a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        mDownloadsSize = downloadsResult.totalAccountedSize();
7777a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        mSystemSize = systemSize;
7877a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        mPackageStats = stats;
7977a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    }
8077a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
8177a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    /**
8277a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi     * Dumps the storage collection output to a file.
8377a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi     * @param file File to write the output into.
8477a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi     * @throws FileNotFoundException
8577a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi     */
8677a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    public void dumpToFile(File file) throws FileNotFoundException {
8777a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        PrintWriter pw = new PrintWriter(file);
8877a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        JSONObject representation = getJsonRepresentation();
8977a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        if (representation != null) {
9077a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            pw.println(representation);
9177a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        }
9277a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        pw.close();
9377a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    }
9477a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
9577a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    private JSONObject getJsonRepresentation() {
9677a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        JSONObject json = new JSONObject();
9777a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        try {
9877a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            json.put(LAST_QUERY_TIMESTAMP_KEY, System.currentTimeMillis());
9977a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            json.put(PHOTOS_KEY, mResult.imagesSize);
10077a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            json.put(VIDEOS_KEY, mResult.videosSize);
10177a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            json.put(AUDIO_KEY, mResult.audioSize);
10277a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            json.put(DOWNLOADS_KEY, mDownloadsSize);
10377a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            json.put(SYSTEM_KEY, mSystemSize);
10477a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            json.put(MISC_KEY, mResult.miscSize);
10577a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            addAppsToJson(json);
10677a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        } catch (JSONException e) {
10777a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            Log.e(TAG, e.toString());
10877a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            return null;
10977a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        }
11077a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
11177a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        return json;
11277a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    }
11377a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
11477a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    private void addAppsToJson(JSONObject json) throws JSONException {
11577a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        JSONArray names = new JSONArray();
11677a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        JSONArray appSizeList = new JSONArray();
11777a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        JSONArray cacheSizeList = new JSONArray();
11877a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
11977a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        long appSizeSum = 0L;
12077a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        long cacheSizeSum = 0L;
12177a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        boolean isExternal = Environment.isExternalStorageEmulated();
122e47eac74f7c80738017734342f66b615189e7596Daniel Nishi        for (Map.Entry<String, PackageStats> entry : filterOnlyPrimaryUser().entrySet()) {
12377a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            PackageStats stat = entry.getValue();
12477a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            long appSize = stat.codeSize + stat.dataSize;
12577a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            long cacheSize = stat.cacheSize;
12677a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            if (isExternal) {
12777a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi                appSize += stat.externalCodeSize + stat.externalDataSize;
12877a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi                cacheSize += stat.externalCacheSize;
12977a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            }
13077a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            appSizeSum += appSize;
13177a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            cacheSizeSum += cacheSize;
13277a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
13377a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            names.put(stat.packageName);
13477a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            appSizeList.put(appSize);
13577a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            cacheSizeList.put(cacheSize);
13677a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        }
13777a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        json.put(PACKAGE_NAMES_KEY, names);
13877a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        json.put(APP_SIZES_KEY, appSizeList);
13977a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        json.put(APP_CACHES_KEY, cacheSizeList);
14077a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        json.put(APP_SIZE_AGG_KEY, appSizeSum);
14177a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        json.put(APP_CACHE_AGG_KEY, cacheSizeSum);
14277a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    }
14377a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi
14477a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    /**
145e47eac74f7c80738017734342f66b615189e7596Daniel Nishi     * A given package may exist for multiple users with distinct sizes. This function filters
146e47eac74f7c80738017734342f66b615189e7596Daniel Nishi     * the packages that do not belong to user 0 out to ensure that we get good stats for a subset.
14777a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi     * @return A mapping of package name to merged package stats.
14877a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi     */
149e47eac74f7c80738017734342f66b615189e7596Daniel Nishi    private ArrayMap<String, PackageStats> filterOnlyPrimaryUser() {
15077a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        ArrayMap<String, PackageStats> packageMap = new ArrayMap<>();
15177a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        for (PackageStats stat : mPackageStats) {
152e47eac74f7c80738017734342f66b615189e7596Daniel Nishi            if (stat.userHandle != UserHandle.USER_SYSTEM) {
153e47eac74f7c80738017734342f66b615189e7596Daniel Nishi                continue;
154e47eac74f7c80738017734342f66b615189e7596Daniel Nishi            }
155e47eac74f7c80738017734342f66b615189e7596Daniel Nishi
15677a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            PackageStats existingStats = packageMap.get(stat.packageName);
15777a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            if (existingStats != null) {
15877a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi                existingStats.cacheSize += stat.cacheSize;
15977a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi                existingStats.codeSize += stat.codeSize;
16077a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi                existingStats.dataSize += stat.dataSize;
16177a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi                existingStats.externalCacheSize += stat.externalCacheSize;
16277a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi                existingStats.externalCodeSize += stat.externalCodeSize;
16377a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi                existingStats.externalDataSize += stat.externalDataSize;
16477a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            } else {
16577a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi                packageMap.put(stat.packageName, new PackageStats(stat));
16677a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi            }
16777a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        }
16877a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi        return packageMap;
16977a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi    }
17077a78c6f4412dccc58075685ad77f3e41d85f2e4Daniel Nishi}