chromium/chrome/browser/android/metrics/java/src/org/chromium/chrome/browser/metrics/PackageMetrics.java

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.metrics;

import android.app.usage.StorageStats;
import android.app.usage.StorageStatsManager;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Build;
import android.os.Environment;
import android.os.Process;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;

import androidx.annotation.RequiresApi;

import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.components.browser_ui.util.ConversionUtils;

import java.io.IOException;
import java.util.UUID;

/** Records UMA about the size of data, cache, and code size on disk for Android. */
public class PackageMetrics {
    private static final String TAG = "PackageMetrics";

    private static class PackageMetricsData {
        public long codeSize;
        public long dataSize;
        public long cacheSize;
    }

    @RequiresApi(26)
    private static PackageMetricsData getPackageStatsForAndroidO() {
        Context context = ContextUtils.getApplicationContext();
        StorageManager storageManager = context.getSystemService(StorageManager.class);
        StorageStatsManager storageStatsManager =
                context.getSystemService(StorageStatsManager.class);
        if (storageManager == null || storageStatsManager == null) {
            Log.e(TAG, "StorageManager or StorageStatsManager is not found");
            return null;
        }

        String packageName = context.getPackageName();
        PackageMetricsData pmd = new PackageMetricsData();
        for (StorageVolume storageVolume : storageManager.getStorageVolumes()) {
            if (Environment.MEDIA_MOUNTED.equals(storageVolume.getState())) {
                String uuidStr = storageVolume.getUuid();
                UUID uuid = null;
                try {
                    uuid = uuidStr == null ? StorageManager.UUID_DEFAULT : UUID.fromString(uuidStr);
                } catch (IllegalArgumentException ex) {
                    // For SD card, on some phone the IIUD is not in the right format and we need
                    // to ignore it, otherwise the whole call fails.
                    Log.e(TAG, "Could not parse UUID " + uuidStr, ex);
                }
                if (uuid == null) continue;

                try {
                    StorageStats storageStats =
                            storageStatsManager.queryStatsForPackage(
                                    uuid, packageName, Process.myUserHandle());
                    pmd.codeSize += storageStats.getAppBytes();
                    pmd.dataSize += (storageStats.getDataBytes() - storageStats.getCacheBytes());
                    pmd.cacheSize += storageStats.getCacheBytes();
                } catch (IOException | NameNotFoundException | SecurityException ex) {
                    Log.e(TAG, "Error calling into queryStatsForPackage", ex);
                }
            }
        }
        return pmd;
    }

    /**
     * Records UMA about the size of data, cache, and code size on disk for Android.
     * Should be called on background thread since some of the API calls can be slow.
     */
    public static void recordPackageStats() {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return;

        PackageMetricsData data = getPackageStatsForAndroidO();
        if (data != null) {
            RecordHistogram.recordCustomCountHistogram(
                    "Android.PackageStats.DataSize",
                    (int) ConversionUtils.bytesToMegabytes(data.dataSize),
                    1,
                    10000,
                    50);
            RecordHistogram.recordCustomCountHistogram(
                    "Android.PackageStats.CacheSize",
                    (int) ConversionUtils.bytesToMegabytes(data.cacheSize),
                    1,
                    10000,
                    50);
            RecordHistogram.recordSparseHistogram(
                    "Android.PackageStats.CodeSize",
                    (int) ConversionUtils.bytesToMegabytes(data.codeSize));
        }
    }
}