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