1 /* 2 * Copyright (C) 2021 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.tv.settings.library.settingslib; 18 19 import android.app.usage.ExternalStorageStats; 20 import android.app.usage.StorageStats; 21 import android.app.usage.StorageStatsManager; 22 import android.content.Context; 23 import android.content.pm.UserInfo; 24 import android.os.AsyncTask; 25 import android.os.Environment; 26 import android.os.SystemClock; 27 import android.os.UserHandle; 28 import android.os.UserManager; 29 import android.os.storage.StorageVolume; 30 import android.os.storage.VolumeInfo; 31 import android.util.Log; 32 import android.util.SparseArray; 33 import android.util.SparseLongArray; 34 35 import java.io.IOException; 36 import java.lang.ref.WeakReference; 37 import java.util.HashMap; 38 import java.util.List; 39 40 /** 41 * Utility for measuring the disk usage of internal storage or a physical 42 * {@link StorageVolume}. 43 */ 44 public class StorageMeasurement { 45 private static final String TAG = "StorageMeasurement"; 46 47 public static class MeasurementDetails { 48 /** Size of storage device. */ 49 public long totalSize; 50 /** Size of available space. */ 51 public long availSize; 52 /** Size of all cached data. */ 53 public long cacheSize; 54 55 /** 56 * Total disk space used by everything. 57 * <p> 58 * Key is {@link UserHandle}. 59 */ 60 public SparseLongArray usersSize = new SparseLongArray(); 61 62 /** 63 * Total disk space used by apps. 64 * <p> 65 * Key is {@link UserHandle}. 66 */ 67 public SparseLongArray appsSize = new SparseLongArray(); 68 69 /** 70 * Total disk space used by media on shared storage. 71 * <p> 72 * First key is {@link UserHandle}. Second key is media type, such as 73 * {@link Environment#DIRECTORY_PICTURES}. 74 */ 75 public SparseArray<HashMap<String, Long>> mediaSize = new SparseArray<>(); 76 77 /** 78 * Total disk space used by non-media on shared storage. 79 * <p> 80 * Key is {@link UserHandle}. 81 */ 82 public SparseLongArray miscSize = new SparseLongArray(); 83 84 @Override toString()85 public String toString() { 86 return "MeasurementDetails: [totalSize: " + totalSize + " availSize: " + availSize 87 + " cacheSize: " + cacheSize + " mediaSize: " + mediaSize 88 + " miscSize: " + miscSize + "usersSize: " + usersSize + "]"; 89 } 90 } 91 92 public interface MeasurementReceiver { onDetailsChanged(MeasurementDetails details)93 void onDetailsChanged(MeasurementDetails details); 94 } 95 96 private WeakReference<MeasurementReceiver> 97 mReceiver; 98 99 private final Context mContext; 100 private final UserManager mUser; 101 private final StorageStatsManager mStats; 102 103 private final VolumeInfo mVolume; 104 private final VolumeInfo mSharedVolume; 105 StorageMeasurement(Context context, VolumeInfo volume, VolumeInfo sharedVolume)106 public StorageMeasurement(Context context, VolumeInfo volume, VolumeInfo sharedVolume) { 107 mContext = context.getApplicationContext(); 108 mUser = mContext.getSystemService(UserManager.class); 109 mStats = mContext.getSystemService(StorageStatsManager.class); 110 111 mVolume = volume; 112 mSharedVolume = sharedVolume; 113 } 114 setReceiver(MeasurementReceiver receiver)115 public void setReceiver(MeasurementReceiver receiver) { 116 if (mReceiver == null || mReceiver.get() == null) { 117 mReceiver = new WeakReference<MeasurementReceiver>(receiver); 118 } 119 } 120 forceMeasure()121 public void forceMeasure() { 122 measure(); 123 } 124 measure()125 public void measure() { 126 new StorageMeasurement.MeasureTask().execute(); 127 } 128 onDestroy()129 public void onDestroy() { 130 mReceiver = null; 131 } 132 133 private class MeasureTask extends AsyncTask<Void, Void, MeasurementDetails> { 134 @Override doInBackground(Void... params)135 protected MeasurementDetails doInBackground(Void... params) { 136 return measureExactStorage(); 137 } 138 139 @Override onPostExecute(MeasurementDetails result)140 protected void onPostExecute(MeasurementDetails result) { 141 final MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null; 142 if (receiver != null) { 143 receiver.onDetailsChanged(result); 144 } 145 } 146 } 147 measureExactStorage()148 private MeasurementDetails measureExactStorage() { 149 final List<UserInfo> users = mUser.getUsers(); 150 151 final long start = SystemClock.elapsedRealtime(); 152 153 final MeasurementDetails details = new MeasurementDetails(); 154 if (mVolume == null) return details; 155 156 if (mVolume.getType() == VolumeInfo.TYPE_PUBLIC 157 || mVolume.getType() == VolumeInfo.TYPE_STUB) { 158 details.totalSize = mVolume.getPath().getTotalSpace(); 159 details.availSize = mVolume.getPath().getUsableSpace(); 160 return details; 161 } 162 163 try { 164 details.totalSize = mStats.getTotalBytes(mVolume.fsUuid); 165 details.availSize = mStats.getFreeBytes(mVolume.fsUuid); 166 } catch (IOException e) { 167 // The storage volume became null while we were measuring it. 168 Log.w(TAG, e); 169 return details; 170 } 171 172 final long finishTotal = SystemClock.elapsedRealtime(); 173 Log.d(TAG, "Measured total storage in " + (finishTotal - start) + "ms"); 174 175 if (mSharedVolume != null && mSharedVolume.isMountedReadable()) { 176 for (UserInfo user : users) { 177 final HashMap<String, Long> mediaMap = new HashMap<>(); 178 details.mediaSize.put(user.id, mediaMap); 179 180 final ExternalStorageStats stats; 181 try { 182 stats = mStats.queryExternalStatsForUser(mSharedVolume.fsUuid, 183 UserHandle.of(user.id)); 184 } catch (IOException e) { 185 Log.w(TAG, e); 186 continue; 187 } 188 189 addValue(details.usersSize, user.id, stats.getTotalBytes()); 190 191 // Track detailed data types 192 mediaMap.put(Environment.DIRECTORY_MUSIC, stats.getAudioBytes()); 193 mediaMap.put(Environment.DIRECTORY_MOVIES, stats.getVideoBytes()); 194 mediaMap.put(Environment.DIRECTORY_PICTURES, stats.getImageBytes()); 195 196 final long miscBytes = stats.getTotalBytes() - stats.getAudioBytes() 197 - stats.getVideoBytes() - stats.getImageBytes(); 198 addValue(details.miscSize, user.id, miscBytes); 199 } 200 } 201 202 final long finishShared = SystemClock.elapsedRealtime(); 203 Log.d(TAG, "Measured shared storage in " + (finishShared - finishTotal) + "ms"); 204 205 if ((mVolume.getType() == VolumeInfo.TYPE_PRIVATE) && mVolume.isMountedReadable()) { 206 for (UserInfo user : users) { 207 final StorageStats stats; 208 try { 209 stats = mStats.queryStatsForUser(mVolume.fsUuid, UserHandle.of(user.id)); 210 } catch (IOException e) { 211 Log.w(TAG, e); 212 continue; 213 } 214 215 // Only count code once against current user 216 if (user.id == UserHandle.myUserId()) { 217 addValue(details.usersSize, user.id, stats.getAppBytes()); 218 } 219 220 addValue(details.usersSize, user.id, stats.getDataBytes()); 221 addValue(details.appsSize, user.id, stats.getAppBytes() + stats.getDataBytes()); 222 223 details.cacheSize += stats.getCacheBytes(); 224 } 225 } 226 227 final long finishPrivate = SystemClock.elapsedRealtime(); 228 Log.d(TAG, "Measured private storage in " + (finishPrivate - finishShared) + "ms"); 229 230 return details; 231 } 232 addValue(SparseLongArray array, int key, long value)233 private static void addValue(SparseLongArray array, int key, long value) { 234 array.put(key, array.get(key) + value); 235 } 236 } 237