1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.settings.storage; 18 19 import static android.content.pm.ApplicationInfo.CATEGORY_AUDIO; 20 import static android.content.pm.ApplicationInfo.CATEGORY_GAME; 21 import static android.content.pm.ApplicationInfo.CATEGORY_IMAGE; 22 import static android.content.pm.ApplicationInfo.CATEGORY_VIDEO; 23 24 import android.content.Context; 25 import android.content.pm.ApplicationInfo; 26 import android.content.pm.PackageManager; 27 import android.content.pm.PackageManager.NameNotFoundException; 28 import android.content.pm.UserInfo; 29 import android.os.UserHandle; 30 import android.util.ArraySet; 31 import android.util.SparseArray; 32 33 import androidx.annotation.VisibleForTesting; 34 35 import com.android.car.settings.common.AsyncLoader; 36 import com.android.car.settings.common.Logger; 37 import com.android.car.settings.profiles.ProfileHelper; 38 import com.android.settingslib.applications.StorageStatsSource; 39 40 import java.io.IOException; 41 import java.util.List; 42 43 /** 44 * {@link StorageAsyncLoader} is a Loader which loads categorized app information and external stats 45 * for all users. 46 * 47 * <p>Class is taken from {@link com.android.settings.deviceinfo.storage.StorageAsyncLoader} 48 */ 49 public class StorageAsyncLoader 50 extends AsyncLoader<SparseArray<StorageAsyncLoader.AppsStorageResult>> { 51 private static final Logger LOG = new Logger(StorageAsyncLoader.class); 52 53 private final StorageStatsSource mStatsManager; 54 private final PackageManager mPackageManager; 55 private final ProfileHelper mProfileHelper; 56 StorageAsyncLoader(Context context, StorageStatsSource source)57 public StorageAsyncLoader(Context context, StorageStatsSource source) { 58 this(context, source, context.getPackageManager(), ProfileHelper.getInstance(context)); 59 } 60 61 @VisibleForTesting StorageAsyncLoader(Context context, StorageStatsSource source, PackageManager packageManager, ProfileHelper profileHelper)62 StorageAsyncLoader(Context context, StorageStatsSource source, 63 PackageManager packageManager, ProfileHelper profileHelper) { 64 super(context); 65 mStatsManager = source; 66 mPackageManager = packageManager; 67 mProfileHelper = profileHelper; 68 } 69 70 @Override loadInBackground()71 public SparseArray<AppsStorageResult> loadInBackground() { 72 ArraySet<String> seenPackages = new ArraySet<>(); 73 SparseArray<AppsStorageResult> result = new SparseArray<>(); 74 List<UserInfo> infos = mProfileHelper.getAllProfiles(); 75 for (int i = 0, userCount = infos.size(); i < userCount; i++) { 76 UserInfo info = infos.get(i); 77 result.put(info.id, getStorageResultForUser(info.id, seenPackages)); 78 } 79 return result; 80 } 81 getStorageResultForUser(int userId, ArraySet<String> seenPackages)82 private AppsStorageResult getStorageResultForUser(int userId, ArraySet<String> seenPackages) { 83 LOG.d("Loading apps"); 84 List<ApplicationInfo> applicationInfos = 85 mPackageManager.getInstalledApplicationsAsUser(/* getAllInstalledApplications= */ 0, 86 userId); 87 UserHandle myUser = UserHandle.of(userId); 88 long gameAppSize = 0; 89 long musicAppsSize = 0; 90 long videoAppsSize = 0; 91 long photosAppsSize = 0; 92 long otherAppsSize = 0; 93 for (int i = 0, size = applicationInfos.size(); i < size; i++) { 94 ApplicationInfo app = applicationInfos.get(i); 95 StorageStatsSource.AppStorageStats stats; 96 try { 97 stats = mStatsManager.getStatsForPackage(/* volumeUuid= */ null, app.packageName, 98 myUser); 99 } catch (NameNotFoundException | IOException e) { 100 // This may happen if the package was removed during our calculation. 101 LOG.w("App unexpectedly not found", e); 102 continue; 103 } 104 105 long dataSize = stats.getDataBytes(); 106 long cacheQuota = mStatsManager.getCacheQuotaBytes(/* volumeUuid= */ null, app.uid); 107 long cacheBytes = stats.getCacheBytes(); 108 long blamedSize = dataSize; 109 // Technically, we could show overages as freeable on the storage settings screen. 110 // If the app is using more cache than its quota, we would accidentally subtract the 111 // overage from the system size (because it shows up as unused) during our attribution. 112 // Thus, we cap the attribution at the quota size. 113 if (cacheQuota < cacheBytes) { 114 blamedSize = blamedSize - cacheBytes + cacheQuota; 115 } 116 117 // This isn't quite right because it slams the first user by user id with the whole code 118 // size, but this ensures that we count all apps seen once. 119 if (!seenPackages.contains(app.packageName)) { 120 blamedSize += stats.getCodeBytes(); 121 seenPackages.add(app.packageName); 122 } 123 124 switch (app.category) { 125 case CATEGORY_GAME: 126 gameAppSize += blamedSize; 127 break; 128 case CATEGORY_AUDIO: 129 musicAppsSize += blamedSize; 130 break; 131 case CATEGORY_VIDEO: 132 videoAppsSize += blamedSize; 133 break; 134 case CATEGORY_IMAGE: 135 photosAppsSize += blamedSize; 136 break; 137 default: 138 // The deprecated game flag does not set the category. 139 if ((app.flags & ApplicationInfo.FLAG_IS_GAME) != 0) { 140 gameAppSize += blamedSize; 141 break; 142 } 143 otherAppsSize += blamedSize; 144 break; 145 } 146 } 147 148 AppsStorageResult result = new AppsStorageResult(gameAppSize, musicAppsSize, photosAppsSize, 149 videoAppsSize, otherAppsSize); 150 151 LOG.d("Loading external stats"); 152 try { 153 result.mStorageStats = mStatsManager.getExternalStorageStats(null, 154 UserHandle.of(userId)); 155 } catch (IOException e) { 156 LOG.w("External stats not loaded" + e); 157 } 158 LOG.d("Obtaining result completed"); 159 return result; 160 } 161 162 /** 163 * Class to hold the result for different categories for storage. 164 */ 165 public static class AppsStorageResult { 166 private final long mGamesSize; 167 private final long mMusicAppsSize; 168 private final long mPhotosAppsSize; 169 private final long mVideoAppsSize; 170 private final long mOtherAppsSize; 171 private long mCacheSize; 172 private StorageStatsSource.ExternalStorageStats mStorageStats; 173 AppsStorageResult(long gamesSize, long musicAppsSize, long photosAppsSize, long videoAppsSize, long otherAppsSize)174 AppsStorageResult(long gamesSize, long musicAppsSize, long photosAppsSize, 175 long videoAppsSize, long otherAppsSize) { 176 mGamesSize = gamesSize; 177 mMusicAppsSize = musicAppsSize; 178 mPhotosAppsSize = photosAppsSize; 179 mVideoAppsSize = videoAppsSize; 180 mOtherAppsSize = otherAppsSize; 181 } 182 183 /** 184 * Returns the size in bytes used by the applications of category {@link CATEGORY_GAME}. 185 */ getGamesSize()186 public long getGamesSize() { 187 return mGamesSize; 188 } 189 190 /** 191 * Returns the size in bytes used by the applications of category {@link CATEGORY_AUDIO}. 192 */ getMusicAppsSize()193 public long getMusicAppsSize() { 194 return mMusicAppsSize; 195 } 196 197 /** 198 * Returns the size in bytes used by the applications of category {@link CATEGORY_IMAGE}. 199 */ getPhotosAppsSize()200 public long getPhotosAppsSize() { 201 return mPhotosAppsSize; 202 } 203 204 /** 205 * Returns the size in bytes used by the applications of category {@link CATEGORY_VIDEO}. 206 */ getVideoAppsSize()207 public long getVideoAppsSize() { 208 return mVideoAppsSize; 209 } 210 211 /** 212 * Returns the size in bytes used by the applications not assigned to one of the other 213 * categories. 214 */ getOtherAppsSize()215 public long getOtherAppsSize() { 216 return mOtherAppsSize; 217 } 218 219 /** 220 * Returns the cached size in bytes. 221 */ getCacheSize()222 public long getCacheSize() { 223 return mCacheSize; 224 } 225 226 /** 227 * Sets the storage cached size. 228 */ setCacheSize(long cacheSize)229 public void setCacheSize(long cacheSize) { 230 this.mCacheSize = cacheSize; 231 } 232 233 /** 234 * Returns the size in bytes for external storage of mounted device. 235 */ getExternalStats()236 public StorageStatsSource.ExternalStorageStats getExternalStats() { 237 return mStorageStats; 238 } 239 240 /** 241 * Sets the size in bytes for the external storage. 242 */ setExternalStats( StorageStatsSource.ExternalStorageStats externalStats)243 public void setExternalStats( 244 StorageStatsSource.ExternalStorageStats externalStats) { 245 this.mStorageStats = externalStats; 246 } 247 } 248 } 249