• 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 
89             result.imagesSize = getFilesSize(info.id, MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
90                     null /* queryArgs */);
91             result.videosSize = getFilesSize(info.id, MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
92                     null /* queryArgs */);
93             result.audioSize = getFilesSize(info.id, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
94                     null /* queryArgs */);
95 
96             final Bundle documentsAndOtherQueryArgs = new Bundle();
97             documentsAndOtherQueryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
98                     FileColumns.MEDIA_TYPE + "!=" + FileColumns.MEDIA_TYPE_IMAGE
99                     + " AND " + FileColumns.MEDIA_TYPE + "!=" + FileColumns.MEDIA_TYPE_VIDEO
100                     + " AND " + FileColumns.MEDIA_TYPE + "!=" + FileColumns.MEDIA_TYPE_AUDIO
101                     + " AND " + FileColumns.MIME_TYPE + " IS NOT NULL");
102             result.documentsAndOtherSize = getFilesSize(info.id,
103                     MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
104                     documentsAndOtherQueryArgs);
105 
106             final Bundle trashQueryArgs = new Bundle();
107             trashQueryArgs.putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY);
108             result.trashSize = getFilesSize(info.id,
109                     MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), trashQueryArgs);
110 
111             results.put(info.id, result);
112         }
113         return results;
114     }
115 
getFilesSize(int userId, Uri uri, Bundle queryArgs)116     private long getFilesSize(int userId, Uri uri, Bundle queryArgs) {
117         final Context perUserContext;
118         try {
119             perUserContext = getContext().createPackageContextAsUser(
120                 getContext().getApplicationContext().getPackageName(),
121                 0 /* flags= */,
122                 UserHandle.of(userId));
123         } catch (NameNotFoundException e) {
124             Log.e(TAG, "Not able to get Context for user ID " + userId);
125             return 0L;
126         }
127 
128         try (Cursor cursor = perUserContext.getContentResolver().query(
129                 uri,
130                 new String[] {"sum(" + MediaColumns.SIZE + ")"},
131                 queryArgs,
132                 null /* cancellationSignal */)) {
133             if (cursor == null) {
134                 return 0L;
135             }
136             return cursor.moveToFirst() ? cursor.getLong(0) : 0L;
137         }
138     }
139 
getAppsAndGamesSize(int userId)140     private StorageResult getAppsAndGamesSize(int userId) {
141         Log.d(TAG, "Loading apps");
142         final List<ApplicationInfo> applicationInfos =
143                 mPackageManager.getInstalledApplicationsAsUser(0, userId);
144         final StorageResult result = new StorageResult();
145         final UserHandle myUser = UserHandle.of(userId);
146         for (int i = 0, size = applicationInfos.size(); i < size; i++) {
147             final ApplicationInfo app = applicationInfos.get(i);
148 
149             StorageStatsSource.AppStorageStats stats;
150             try {
151                 stats = mStatsManager.getStatsForPackage(mUuid, app.packageName, myUser);
152             } catch (NameNotFoundException | IOException e) {
153                 // This may happen if the package was removed during our calculation.
154                 Log.w(TAG, "App unexpectedly not found", e);
155                 continue;
156             }
157 
158             final long dataSize = stats.getDataBytes();
159             final long cacheQuota = mStatsManager.getCacheQuotaBytes(mUuid, app.uid);
160             final long cacheBytes = stats.getCacheBytes();
161             long blamedSize = dataSize + stats.getCodeBytes();
162             // Technically, we could overages as freeable on the storage settings screen.
163             // If the app is using more cache than its quota, we would accidentally subtract the
164             // overage from the system size (because it shows up as unused) during our attribution.
165             // Thus, we cap the attribution at the quota size.
166             if (cacheQuota < cacheBytes) {
167                 blamedSize = blamedSize - cacheBytes + cacheQuota;
168             }
169 
170             // Code bytes may share between different profiles. To know all the duplicate code size
171             // and we can get a reasonable system size in StorageItemPreferenceController.
172             if (mSeenPackages.contains(app.packageName)) {
173                 result.duplicateCodeSize += stats.getCodeBytes();
174             } else {
175                 mSeenPackages.add(app.packageName);
176             }
177 
178             switch (app.category) {
179                 case CATEGORY_GAME:
180                     result.gamesSize += blamedSize;
181                     break;
182                 case CATEGORY_AUDIO:
183                 case CATEGORY_VIDEO:
184                 case CATEGORY_IMAGE:
185                     result.allAppsExceptGamesSize += blamedSize;
186                     break;
187                 default:
188                     // The deprecated game flag does not set the category.
189                     if ((app.flags & ApplicationInfo.FLAG_IS_GAME) != 0) {
190                         result.gamesSize += blamedSize;
191                         break;
192                     }
193                     result.allAppsExceptGamesSize += blamedSize;
194                     break;
195             }
196         }
197 
198         Log.d(TAG, "Loading external stats");
199         try {
200             result.externalStats = mStatsManager.getExternalStorageStats(mUuid,
201                     UserHandle.of(userId));
202         } catch (IOException e) {
203             Log.w(TAG, e);
204         }
205         Log.d(TAG, "Obtaining result completed");
206         return result;
207     }
208 
209     @Override
onDiscardResult(SparseArray<StorageResult> result)210     protected void onDiscardResult(SparseArray<StorageResult> result) {
211     }
212 
213     /** Storage result for displaying file categories size in Storage Settings. */
214     public static class StorageResult {
215         // APP based sizes.
216         public long gamesSize;
217         public long allAppsExceptGamesSize;
218 
219         // File based sizes.
220         public long audioSize;
221         public long imagesSize;
222         public long videosSize;
223         public long documentsAndOtherSize;
224         public long trashSize;
225 
226         public long cacheSize;
227         public long duplicateCodeSize;
228         public StorageStatsSource.ExternalStorageStats externalStats;
229     }
230 
231     /**
232      * ResultHandler defines a destination of data which can handle a result from
233      * {@link StorageAsyncLoader}.
234      */
235     public interface ResultHandler {
236         /** Overrides this method to get storage result once it's available. */
handleResult(SparseArray<StorageResult> result)237         void handleResult(SparseArray<StorageResult> result);
238     }
239 }
240