1 /* 2 * Copyright (C) 2016 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.server.storage; 18 19 import android.annotation.NonNull; 20 import android.app.usage.StorageStats; 21 import android.app.usage.StorageStatsManager; 22 import android.content.Context; 23 import android.content.pm.ApplicationInfo; 24 import android.content.pm.PackageManager; 25 import android.content.pm.PackageManager.NameNotFoundException; 26 import android.content.pm.PackageStats; 27 import android.content.pm.UserInfo; 28 import android.os.Handler; 29 import android.os.Looper; 30 import android.os.Message; 31 import android.os.UserManager; 32 import android.os.storage.VolumeInfo; 33 import android.util.Log; 34 35 import com.android.internal.os.BackgroundThread; 36 import com.android.internal.util.Preconditions; 37 38 import java.io.IOException; 39 import java.util.ArrayList; 40 import java.util.List; 41 import java.util.Objects; 42 import java.util.concurrent.CompletableFuture; 43 import java.util.concurrent.ExecutionException; 44 import java.util.concurrent.TimeUnit; 45 import java.util.concurrent.TimeoutException; 46 47 /** 48 * AppCollector asynchronously collects package sizes. 49 */ 50 public class AppCollector { 51 private static String TAG = "AppCollector"; 52 53 private CompletableFuture<List<PackageStats>> mStats; 54 private final BackgroundHandler mBackgroundHandler; 55 56 /** 57 * Constrcuts a new AppCollector which runs on the provided volume. 58 * @param context Android context used to get 59 * @param volume Volume to check for apps. 60 */ 61 public AppCollector(Context context, @NonNull VolumeInfo volume) { 62 Preconditions.checkNotNull(volume); 63 64 mBackgroundHandler = new BackgroundHandler(BackgroundThread.get().getLooper(), 65 volume, 66 context.getPackageManager(), 67 (UserManager) context.getSystemService(Context.USER_SERVICE), 68 (StorageStatsManager) context.getSystemService(Context.STORAGE_STATS_SERVICE)); 69 } 70 71 /** 72 * Returns a list of package stats for the context and volume. Note that in a multi-user 73 * environment, this may return stats for the same package multiple times. These "duplicate" 74 * entries will have the package stats for the package for a given user, not the package in 75 * aggregate. 76 * @param timeoutMillis Milliseconds before timing out and returning early with null. 77 */ 78 public List<PackageStats> getPackageStats(long timeoutMillis) { 79 synchronized(this) { 80 if (mStats == null) { 81 mStats = new CompletableFuture<>(); 82 mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_START_LOADING_SIZES); 83 } 84 } 85 86 List<PackageStats> value = null; 87 try { 88 value = mStats.get(timeoutMillis, TimeUnit.MILLISECONDS); 89 } catch (InterruptedException | ExecutionException e) { 90 Log.e(TAG, "An exception occurred while getting app storage", e); 91 } catch (TimeoutException e) { 92 Log.e(TAG, "AppCollector timed out"); 93 } 94 return value; 95 } 96 97 private class BackgroundHandler extends Handler { 98 static final int MSG_START_LOADING_SIZES = 0; 99 private final VolumeInfo mVolume; 100 private final PackageManager mPm; 101 private final UserManager mUm; 102 private final StorageStatsManager mStorageStatsManager; 103 104 BackgroundHandler(Looper looper, @NonNull VolumeInfo volume, 105 PackageManager pm, UserManager um, StorageStatsManager storageStatsManager) { 106 super(looper); 107 mVolume = volume; 108 mPm = pm; 109 mUm = um; 110 mStorageStatsManager = storageStatsManager; 111 } 112 113 @Override 114 public void handleMessage(Message msg) { 115 switch (msg.what) { 116 case MSG_START_LOADING_SIZES: { 117 List<PackageStats> stats = new ArrayList<>(); 118 List<UserInfo> users = mUm.getUsers(); 119 for (int userCount = 0, userSize = users.size(); 120 userCount < userSize; userCount++) { 121 UserInfo user = users.get(userCount); 122 final List<ApplicationInfo> apps = mPm.getInstalledApplicationsAsUser( 123 PackageManager.MATCH_DISABLED_COMPONENTS, user.id); 124 125 for (int appCount = 0, size = apps.size(); appCount < size; appCount++) { 126 ApplicationInfo app = apps.get(appCount); 127 if (!Objects.equals(app.volumeUuid, mVolume.getFsUuid())) { 128 continue; 129 } 130 131 try { 132 StorageStats storageStats = 133 mStorageStatsManager.queryStatsForPackage(app.storageUuid, 134 app.packageName, user.getUserHandle()); 135 PackageStats packageStats = new PackageStats(app.packageName, 136 user.id); 137 packageStats.cacheSize = storageStats.getCacheBytes(); 138 packageStats.codeSize = storageStats.getCodeBytes(); 139 packageStats.dataSize = storageStats.getDataBytes(); 140 stats.add(packageStats); 141 } catch (NameNotFoundException | IOException e) { 142 Log.e(TAG, "An exception occurred while fetching app size", e); 143 } 144 } 145 } 146 147 mStats.complete(stats); 148 } 149 } 150 } 151 } 152 } 153