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