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