• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.settings.deviceinfo.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.ContentResolver;
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.database.Cursor;
31 import android.net.Uri;
32 import android.os.Bundle;
33 import android.os.UserHandle;
34 import android.os.UserManager;
35 import android.provider.MediaStore;
36 import android.provider.MediaStore.Files.FileColumns;
37 import android.provider.MediaStore.MediaColumns;
38 import android.util.ArraySet;
39 import android.util.Log;
40 import android.util.SparseArray;
41 
42 import com.android.settingslib.applications.StorageStatsSource;
43 import com.android.settingslib.utils.AsyncLoaderCompat;
44 
45 import java.io.IOException;
46 import java.util.Collections;
47 import java.util.List;
48 
49 /**
50  * StorageAsyncLoader is a Loader which loads categorized app information and external stats for all
51  * users
52  */
53 public class StorageAsyncLoader
54         extends AsyncLoaderCompat<SparseArray<StorageAsyncLoader.StorageResult>> {
55     private UserManager mUserManager;
56     private static final String TAG = "StorageAsyncLoader";
57 
58     private String mUuid;
59     private StorageStatsSource mStatsManager;
60     private PackageManager mPackageManager;
61     private ArraySet<String> mSeenPackages;
62 
StorageAsyncLoader(Context context, UserManager userManager, String uuid, StorageStatsSource source, PackageManager pm)63     public StorageAsyncLoader(Context context, UserManager userManager,
64             String uuid, StorageStatsSource source, PackageManager pm) {
65         super(context);
66         mUserManager = userManager;
67         mUuid = uuid;
68         mStatsManager = source;
69         mPackageManager = pm;
70     }
71 
72     @Override
loadInBackground()73     public SparseArray<StorageResult> loadInBackground() {
74         return getStorageResultsForUsers();
75     }
76 
getStorageResultsForUsers()77     private SparseArray<StorageResult> getStorageResultsForUsers() {
78         mSeenPackages = new ArraySet<>();
79         final SparseArray<StorageResult> results = new SparseArray<>();
80         final List<UserInfo> infos = mUserManager.getUsers();
81 
82         // Sort the users by user id ascending.
83         Collections.sort(infos,
84                 (userInfo, otherUser) -> Integer.compare(userInfo.id, otherUser.id));
85 
86         for (UserInfo info : infos) {
87             final StorageResult result = getAppsAndGamesSize(info.id);
88             final Bundle media = new Bundle();
89             media.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, MediaColumns.VOLUME_NAME
90                     + "= '" + MediaStore.VOLUME_EXTERNAL_PRIMARY + "'");
91             result.imagesSize = getFilesSize(info.id, MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
92                     media /* queryArgs */);
93             result.videosSize = getFilesSize(info.id, MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
94                     media /* queryArgs */);
95             result.audioSize = getFilesSize(info.id, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
96                     media /* queryArgs */);
97 
98             final Bundle documentsAndOtherQueryArgs = new Bundle();
99             documentsAndOtherQueryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
100                     FileColumns.MEDIA_TYPE + "!=" + FileColumns.MEDIA_TYPE_IMAGE
101                     + " AND " + FileColumns.MEDIA_TYPE + "!=" + FileColumns.MEDIA_TYPE_VIDEO
102                     + " AND " + FileColumns.MEDIA_TYPE + "!=" + FileColumns.MEDIA_TYPE_AUDIO
103                     + " AND " + FileColumns.MIME_TYPE + " IS NOT NULL");
104             result.documentsAndOtherSize = getFilesSize(info.id,
105                     MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY),
106                     documentsAndOtherQueryArgs);
107 
108             final Bundle trashQueryArgs = new Bundle();
109             trashQueryArgs.putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY);
110             result.trashSize = getFilesSize(info.id,
111                     MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY),
112                     trashQueryArgs);
113 
114             results.put(info.id, result);
115         }
116         return results;
117     }
118 
getFilesSize(int userId, Uri uri, Bundle queryArgs)119     private long getFilesSize(int userId, Uri uri, Bundle queryArgs) {
120         final Context perUserContext;
121         try {
122             perUserContext = getContext().createPackageContextAsUser(
123                 getContext().getApplicationContext().getPackageName(),
124                 0 /* flags= */,
125                 UserHandle.of(userId));
126         } catch (NameNotFoundException e) {
127             Log.e(TAG, "Not able to get Context for user ID " + userId);
128             return 0L;
129         }
130 
131         try (Cursor cursor = perUserContext.getContentResolver().query(
132                 uri,
133                 new String[] {"sum(" + MediaColumns.SIZE + ")"},
134                 queryArgs,
135                 null /* cancellationSignal */)) {
136             if (cursor == null) {
137                 return 0L;
138             }
139             return cursor.moveToFirst() ? cursor.getLong(0) : 0L;
140         }
141     }
142 
getAppsAndGamesSize(int userId)143     private StorageResult getAppsAndGamesSize(int userId) {
144         Log.d(TAG, "Loading apps");
145         final List<ApplicationInfo> applicationInfos =
146                 mPackageManager.getInstalledApplicationsAsUser(0, userId);
147         final StorageResult result = new StorageResult();
148         final UserHandle myUser = UserHandle.of(userId);
149         for (int i = 0, size = applicationInfos.size(); i < size; i++) {
150             final ApplicationInfo app = applicationInfos.get(i);
151 
152             StorageStatsSource.AppStorageStats stats;
153             try {
154                 stats = mStatsManager.getStatsForPackage(mUuid, app.packageName, myUser);
155             } catch (NameNotFoundException | IOException e) {
156                 // This may happen if the package was removed during our calculation.
157                 Log.w(TAG, "App unexpectedly not found", e);
158                 continue;
159             }
160 
161             final long dataSize = stats.getDataBytes();
162             final long cacheQuota = mStatsManager.getCacheQuotaBytes(mUuid, app.uid);
163             final long cacheBytes = stats.getCacheBytes();
164             long blamedSize = dataSize + stats.getCodeBytes();
165             // Technically, we could overages as freeable on the storage settings screen.
166             // If the app is using more cache than its quota, we would accidentally subtract the
167             // overage from the system size (because it shows up as unused) during our attribution.
168             // Thus, we cap the attribution at the quota size.
169             if (cacheQuota < cacheBytes) {
170                 blamedSize = blamedSize - cacheBytes + cacheQuota;
171             }
172 
173             // Code bytes may share between different profiles. To know all the duplicate code size
174             // and we can get a reasonable system size in StorageItemPreferenceController.
175             if (mSeenPackages.contains(app.packageName)) {
176                 result.duplicateCodeSize += stats.getCodeBytes();
177             } else {
178                 mSeenPackages.add(app.packageName);
179             }
180 
181             switch (app.category) {
182                 case CATEGORY_GAME:
183                     result.gamesSize += blamedSize;
184                     break;
185                 case CATEGORY_AUDIO:
186                 case CATEGORY_VIDEO:
187                 case CATEGORY_IMAGE:
188                     result.allAppsExceptGamesSize += blamedSize;
189                     break;
190                 default:
191                     // The deprecated game flag does not set the category.
192                     if ((app.flags & ApplicationInfo.FLAG_IS_GAME) != 0) {
193                         result.gamesSize += blamedSize;
194                         break;
195                     }
196                     result.allAppsExceptGamesSize += blamedSize;
197                     break;
198             }
199         }
200 
201         Log.d(TAG, "Loading external stats");
202         try {
203             result.externalStats = mStatsManager.getExternalStorageStats(mUuid,
204                     UserHandle.of(userId));
205         } catch (IOException e) {
206             Log.w(TAG, e);
207         }
208         Log.d(TAG, "Obtaining result completed");
209         return result;
210     }
211 
212     @Override
onDiscardResult(SparseArray<StorageResult> result)213     protected void onDiscardResult(SparseArray<StorageResult> result) {
214     }
215 
216     /** Storage result for displaying file categories size in Storage Settings. */
217     public static class StorageResult {
218         // APP based sizes.
219         public long gamesSize;
220         public long allAppsExceptGamesSize;
221 
222         // File based sizes.
223         public long audioSize;
224         public long imagesSize;
225         public long videosSize;
226         public long documentsAndOtherSize;
227         public long trashSize;
228 
229         public long cacheSize;
230         public long duplicateCodeSize;
231         public StorageStatsSource.ExternalStorageStats externalStats;
232     }
233 
234     /**
235      * ResultHandler defines a destination of data which can handle a result from
236      * {@link StorageAsyncLoader}.
237      */
238     public interface ResultHandler {
239         /** Overrides this method to get storage result once it's available. */
handleResult(SparseArray<StorageResult> result)240         void handleResult(SparseArray<StorageResult> result);
241     }
242 }
243