blob: d65b5b3599fe025771d0d326278a3084f6fbe956 [file] [log] [blame]
/*
* Copyright (C) 2019 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/LICENSE-2.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.car.settings.storage;
import static android.content.pm.ApplicationInfo.CATEGORY_AUDIO;
import static android.content.pm.ApplicationInfo.CATEGORY_GAME;
import static android.content.pm.ApplicationInfo.CATEGORY_IMAGE;
import static android.content.pm.ApplicationInfo.CATEGORY_VIDEO;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.SparseArray;
import androidx.annotation.VisibleForTesting;
import com.android.car.settings.common.AsyncLoader;
import com.android.car.settings.common.Logger;
import com.android.car.settings.profiles.ProfileHelper;
import com.android.settingslib.applications.StorageStatsSource;
import java.io.IOException;
import java.util.List;
/**
* {@link StorageAsyncLoader} is a Loader which loads categorized app information and external stats
* for all users.
*
* <p>Class is taken from {@link com.android.settings.deviceinfo.storage.StorageAsyncLoader}
*/
public class StorageAsyncLoader
extends AsyncLoader<SparseArray<StorageAsyncLoader.AppsStorageResult>> {
private static final Logger LOG = new Logger(StorageAsyncLoader.class);
private final StorageStatsSource mStatsManager;
private final PackageManager mPackageManager;
private final ProfileHelper mProfileHelper;
public StorageAsyncLoader(Context context, StorageStatsSource source) {
this(context, source, context.getPackageManager(), ProfileHelper.getInstance(context));
}
@VisibleForTesting
StorageAsyncLoader(Context context, StorageStatsSource source,
PackageManager packageManager, ProfileHelper profileHelper) {
super(context);
mStatsManager = source;
mPackageManager = packageManager;
mProfileHelper = profileHelper;
}
@Override
public SparseArray<AppsStorageResult> loadInBackground() {
ArraySet<String> seenPackages = new ArraySet<>();
SparseArray<AppsStorageResult> result = new SparseArray<>();
List<UserInfo> infos = mProfileHelper.getAllProfiles();
for (int i = 0, userCount = infos.size(); i < userCount; i++) {
UserInfo info = infos.get(i);
result.put(info.id, getStorageResultForUser(info.id, seenPackages));
}
return result;
}
private AppsStorageResult getStorageResultForUser(int userId, ArraySet<String> seenPackages) {
LOG.d("Loading apps");
List<ApplicationInfo> applicationInfos =
mPackageManager.getInstalledApplicationsAsUser(/* getAllInstalledApplications= */ 0,
userId);
UserHandle myUser = UserHandle.of(userId);
long gameAppSize = 0;
long musicAppsSize = 0;
long videoAppsSize = 0;
long photosAppsSize = 0;
long otherAppsSize = 0;
for (int i = 0, size = applicationInfos.size(); i < size; i++) {
ApplicationInfo app = applicationInfos.get(i);
StorageStatsSource.AppStorageStats stats;
try {
stats = mStatsManager.getStatsForPackage(/* volumeUuid= */ null, app.packageName,
myUser);
} catch (NameNotFoundException | IOException e) {
// This may happen if the package was removed during our calculation.
LOG.w("App unexpectedly not found", e);
continue;
}
long dataSize = stats.getDataBytes();
long cacheQuota = mStatsManager.getCacheQuotaBytes(/* volumeUuid= */ null, app.uid);
long cacheBytes = stats.getCacheBytes();
long blamedSize = dataSize;
// Technically, we could show overages as freeable on the storage settings screen.
// If the app is using more cache than its quota, we would accidentally subtract the
// overage from the system size (because it shows up as unused) during our attribution.
// Thus, we cap the attribution at the quota size.
if (cacheQuota < cacheBytes) {
blamedSize = blamedSize - cacheBytes + cacheQuota;
}
// This isn't quite right because it slams the first user by user id with the whole code
// size, but this ensures that we count all apps seen once.
if (!seenPackages.contains(app.packageName)) {
blamedSize += stats.getCodeBytes();
seenPackages.add(app.packageName);
}
switch (app.category) {
case CATEGORY_GAME:
gameAppSize += blamedSize;
break;
case CATEGORY_AUDIO:
musicAppsSize += blamedSize;
break;
case CATEGORY_VIDEO:
videoAppsSize += blamedSize;
break;
case CATEGORY_IMAGE:
photosAppsSize += blamedSize;
break;
default:
// The deprecated game flag does not set the category.
if ((app.flags & ApplicationInfo.FLAG_IS_GAME) != 0) {
gameAppSize += blamedSize;
break;
}
otherAppsSize += blamedSize;
break;
}
}
AppsStorageResult result = new AppsStorageResult(gameAppSize, musicAppsSize, photosAppsSize,
videoAppsSize, otherAppsSize);
LOG.d("Loading external stats");
try {
result.mStorageStats = mStatsManager.getExternalStorageStats(null,
UserHandle.of(userId));
} catch (IOException e) {
LOG.w("External stats not loaded" + e);
}
LOG.d("Obtaining result completed");
return result;
}
/**
* Class to hold the result for different categories for storage.
*/
public static class AppsStorageResult {
private final long mGamesSize;
private final long mMusicAppsSize;
private final long mPhotosAppsSize;
private final long mVideoAppsSize;
private final long mOtherAppsSize;
private long mCacheSize;
private StorageStatsSource.ExternalStorageStats mStorageStats;
AppsStorageResult(long gamesSize, long musicAppsSize, long photosAppsSize,
long videoAppsSize, long otherAppsSize) {
mGamesSize = gamesSize;
mMusicAppsSize = musicAppsSize;
mPhotosAppsSize = photosAppsSize;
mVideoAppsSize = videoAppsSize;
mOtherAppsSize = otherAppsSize;
}
/**
* Returns the size in bytes used by the applications of category {@link CATEGORY_GAME}.
*/
public long getGamesSize() {
return mGamesSize;
}
/**
* Returns the size in bytes used by the applications of category {@link CATEGORY_AUDIO}.
*/
public long getMusicAppsSize() {
return mMusicAppsSize;
}
/**
* Returns the size in bytes used by the applications of category {@link CATEGORY_IMAGE}.
*/
public long getPhotosAppsSize() {
return mPhotosAppsSize;
}
/**
* Returns the size in bytes used by the applications of category {@link CATEGORY_VIDEO}.
*/
public long getVideoAppsSize() {
return mVideoAppsSize;
}
/**
* Returns the size in bytes used by the applications not assigned to one of the other
* categories.
*/
public long getOtherAppsSize() {
return mOtherAppsSize;
}
/**
* Returns the cached size in bytes.
*/
public long getCacheSize() {
return mCacheSize;
}
/**
* Sets the storage cached size.
*/
public void setCacheSize(long cacheSize) {
this.mCacheSize = cacheSize;
}
/**
* Returns the size in bytes for external storage of mounted device.
*/
public StorageStatsSource.ExternalStorageStats getExternalStats() {
return mStorageStats;
}
/**
* Sets the size in bytes for the external storage.
*/
public void setExternalStats(
StorageStatsSource.ExternalStorageStats externalStats) {
this.mStorageStats = externalStats;
}
}
}