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