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 17package com.android.server.storage; 18 19import android.annotation.NonNull; 20import android.content.Context; 21import android.content.pm.ApplicationInfo; 22import android.content.pm.IPackageStatsObserver; 23import android.content.pm.PackageManager; 24import android.content.pm.PackageStats; 25import android.content.pm.UserInfo; 26import android.os.Handler; 27import android.os.HandlerThread; 28import android.os.Looper; 29import android.os.Message; 30import android.os.Process; 31import android.os.RemoteException; 32import android.os.UserManager; 33import android.os.storage.VolumeInfo; 34import android.util.Log; 35import com.android.internal.os.BackgroundThread; 36import com.android.internal.util.Preconditions; 37 38import java.util.ArrayList; 39import java.util.List; 40import java.util.Objects; 41import java.util.concurrent.CompletableFuture; 42import java.util.concurrent.ExecutionException; 43import java.util.concurrent.TimeUnit; 44import java.util.concurrent.TimeoutException; 45import java.util.concurrent.atomic.AtomicInteger; 46 47/** 48 * AppCollector asynchronously collects package sizes. 49 */ 50public 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 } 69 70 /** 71 * Returns a list of package stats for the context and volume. Note that in a multi-user 72 * environment, this may return stats for the same package multiple times. These "duplicate" 73 * entries will have the package stats for the package for a given user, not the package in 74 * aggregate. 75 * @param timeoutMillis Milliseconds before timing out and returning early with null. 76 */ 77 public List<PackageStats> getPackageStats(long timeoutMillis) { 78 synchronized(this) { 79 if (mStats == null) { 80 mStats = new CompletableFuture<>(); 81 mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_START_LOADING_SIZES); 82 } 83 } 84 85 List<PackageStats> value = null; 86 try { 87 value = mStats.get(timeoutMillis, TimeUnit.MILLISECONDS); 88 } catch (InterruptedException | ExecutionException e) { 89 Log.e(TAG, "An exception occurred while getting app storage", e); 90 } catch (TimeoutException e) { 91 Log.e(TAG, "AppCollector timed out"); 92 } 93 return value; 94 } 95 96 private class StatsObserver extends IPackageStatsObserver.Stub { 97 private AtomicInteger mCount; 98 private final ArrayList<PackageStats> mPackageStats; 99 100 public StatsObserver(int count) { 101 mCount = new AtomicInteger(count); 102 mPackageStats = new ArrayList<>(count); 103 } 104 105 @Override 106 public void onGetStatsCompleted(PackageStats packageStats, boolean succeeded) 107 throws RemoteException { 108 if (succeeded) { 109 mPackageStats.add(packageStats); 110 } 111 112 if (mCount.decrementAndGet() == 0) { 113 mStats.complete(mPackageStats); 114 } 115 } 116 } 117 118 private class BackgroundHandler extends Handler { 119 static final int MSG_START_LOADING_SIZES = 0; 120 private final VolumeInfo mVolume; 121 private final PackageManager mPm; 122 private final UserManager mUm; 123 124 BackgroundHandler(Looper looper, @NonNull VolumeInfo volume, PackageManager pm, UserManager um) { 125 super(looper); 126 mVolume = volume; 127 mPm = pm; 128 mUm = um; 129 } 130 131 @Override 132 public void handleMessage(Message msg) { 133 switch (msg.what) { 134 case MSG_START_LOADING_SIZES: { 135 final List<ApplicationInfo> apps = mPm.getInstalledApplications( 136 PackageManager.GET_UNINSTALLED_PACKAGES 137 | PackageManager.GET_DISABLED_COMPONENTS); 138 139 final List<ApplicationInfo> volumeApps = new ArrayList<>(); 140 for (ApplicationInfo app : apps) { 141 if (Objects.equals(app.volumeUuid, mVolume.getFsUuid())) { 142 volumeApps.add(app); 143 } 144 } 145 146 List<UserInfo> users = mUm.getUsers(); 147 final int count = users.size() * volumeApps.size(); 148 if (count == 0) { 149 mStats.complete(new ArrayList<>()); 150 } 151 152 // Kick off the async package size query for all apps. 153 final StatsObserver observer = new StatsObserver(count); 154 for (UserInfo user : users) { 155 for (ApplicationInfo app : volumeApps) { 156 mPm.getPackageSizeInfoAsUser(app.packageName, user.id, 157 observer); 158 } 159 } 160 } 161 } 162 } 163 } 164} 165