Home | History | Annotate | Download | only in storage
      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.Context;
     25 import android.content.pm.ApplicationInfo;
     26 import android.content.pm.PackageManager.NameNotFoundException;
     27 import android.content.pm.UserInfo;
     28 import android.os.UserHandle;
     29 import android.os.UserManager;
     30 import android.util.ArraySet;
     31 import android.util.Log;
     32 import android.util.SparseArray;
     33 
     34 import com.android.settingslib.applications.StorageStatsSource;
     35 import com.android.settingslib.utils.AsyncLoader;
     36 import com.android.settingslib.wrapper.PackageManagerWrapper;
     37 
     38 import java.io.IOException;
     39 import java.util.Collections;
     40 import java.util.Comparator;
     41 import java.util.List;
     42 
     43 /**
     44  * StorageAsyncLoader is a Loader which loads categorized app information and external stats for all
     45  * users
     46  */
     47 public class StorageAsyncLoader
     48         extends AsyncLoader<SparseArray<StorageAsyncLoader.AppsStorageResult>> {
     49     private UserManager mUserManager;
     50     private static final String TAG = "StorageAsyncLoader";
     51 
     52     private String mUuid;
     53     private StorageStatsSource mStatsManager;
     54     private PackageManagerWrapper mPackageManager;
     55     private ArraySet<String> mSeenPackages;
     56 
     57     public StorageAsyncLoader(Context context, UserManager userManager,
     58             String uuid, StorageStatsSource source, PackageManagerWrapper pm) {
     59         super(context);
     60         mUserManager = userManager;
     61         mUuid = uuid;
     62         mStatsManager = source;
     63         mPackageManager = pm;
     64     }
     65 
     66     @Override
     67     public SparseArray<AppsStorageResult> loadInBackground() {
     68         return loadApps();
     69     }
     70 
     71     private SparseArray<AppsStorageResult> loadApps() {
     72         mSeenPackages = new ArraySet<>();
     73         SparseArray<AppsStorageResult> result = new SparseArray<>();
     74         List<UserInfo> infos = mUserManager.getUsers();
     75         // Sort the users by user id ascending.
     76         Collections.sort(
     77                 infos,
     78                 new Comparator<UserInfo>() {
     79                     @Override
     80                     public int compare(UserInfo userInfo, UserInfo otherUser) {
     81                         return Integer.compare(userInfo.id, otherUser.id);
     82                     }
     83                 });
     84         for (int i = 0, userCount = infos.size(); i < userCount; i++) {
     85             UserInfo info = infos.get(i);
     86             result.put(info.id, getStorageResultForUser(info.id));
     87         }
     88         return result;
     89     }
     90 
     91     private AppsStorageResult getStorageResultForUser(int userId) {
     92         Log.d(TAG, "Loading apps");
     93         List<ApplicationInfo> applicationInfos =
     94                 mPackageManager.getInstalledApplicationsAsUser(0, userId);
     95         AppsStorageResult result = new AppsStorageResult();
     96         UserHandle myUser = UserHandle.of(userId);
     97         for (int i = 0, size = applicationInfos.size(); i < size; i++) {
     98             ApplicationInfo app = applicationInfos.get(i);
     99 
    100             StorageStatsSource.AppStorageStats stats;
    101             try {
    102                 stats = mStatsManager.getStatsForPackage(mUuid, app.packageName, myUser);
    103             } catch (NameNotFoundException | IOException e) {
    104                 // This may happen if the package was removed during our calculation.
    105                 Log.w(TAG, "App unexpectedly not found", e);
    106                 continue;
    107             }
    108 
    109             final long dataSize = stats.getDataBytes();
    110             final long cacheQuota = mStatsManager.getCacheQuotaBytes(mUuid, app.uid);
    111             final long cacheBytes = stats.getCacheBytes();
    112             long blamedSize = dataSize;
    113             // Technically, we could overages as freeable on the storage settings screen.
    114             // If the app is using more cache than its quota, we would accidentally subtract the
    115             // overage from the system size (because it shows up as unused) during our attribution.
    116             // Thus, we cap the attribution at the quota size.
    117             if (cacheQuota < cacheBytes) {
    118                 blamedSize = blamedSize - cacheBytes + cacheQuota;
    119             }
    120 
    121             // This isn't quite right because it slams the first user by user id with the whole code
    122             // size, but this ensures that we count all apps seen once.
    123             if (!mSeenPackages.contains(app.packageName)) {
    124                 blamedSize += stats.getCodeBytes();
    125                 mSeenPackages.add(app.packageName);
    126             }
    127 
    128             switch (app.category) {
    129                 case CATEGORY_GAME:
    130                     result.gamesSize += blamedSize;
    131                     break;
    132                 case CATEGORY_AUDIO:
    133                     result.musicAppsSize += blamedSize;
    134                     break;
    135                 case CATEGORY_VIDEO:
    136                     result.videoAppsSize += blamedSize;
    137                     break;
    138                 case CATEGORY_IMAGE:
    139                     result.photosAppsSize += blamedSize;
    140                     break;
    141                 default:
    142                     // The deprecated game flag does not set the category.
    143                     if ((app.flags & ApplicationInfo.FLAG_IS_GAME) != 0) {
    144                         result.gamesSize += blamedSize;
    145                         break;
    146                     }
    147                     result.otherAppsSize += blamedSize;
    148                     break;
    149             }
    150         }
    151 
    152         Log.d(TAG, "Loading external stats");
    153         try {
    154             result.externalStats = mStatsManager.getExternalStorageStats(mUuid,
    155                     UserHandle.of(userId));
    156         } catch (IOException e) {
    157             Log.w(TAG, e);
    158         }
    159         Log.d(TAG, "Obtaining result completed");
    160         return result;
    161     }
    162 
    163     @Override
    164     protected void onDiscardResult(SparseArray<AppsStorageResult> result) {
    165     }
    166 
    167     public static class AppsStorageResult {
    168         public long gamesSize;
    169         public long musicAppsSize;
    170         public long photosAppsSize;
    171         public long videoAppsSize;
    172         public long otherAppsSize;
    173         public long cacheSize;
    174         public StorageStatsSource.ExternalStorageStats externalStats;
    175     }
    176 
    177     /**
    178      * ResultHandler defines a destination of data which can handle a result from
    179      * {@link StorageAsyncLoader}.
    180      */
    181     public interface ResultHandler {
    182         void handleResult(SparseArray<AppsStorageResult> result);
    183     }
    184 }
    185