1 /* 2 * Copyright (C) 2011 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.settingslib.deviceinfo; 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> mReceiver; 97 98 private final Context mContext; 99 private final UserManager mUser; 100 private final StorageStatsManager mStats; 101 102 private final VolumeInfo mVolume; 103 private final VolumeInfo mSharedVolume; 104 StorageMeasurement(Context context, VolumeInfo volume, VolumeInfo sharedVolume)105 public StorageMeasurement(Context context, VolumeInfo volume, VolumeInfo sharedVolume) { 106 mContext = context.getApplicationContext(); 107 mUser = mContext.getSystemService(UserManager.class); 108 mStats = mContext.getSystemService(StorageStatsManager.class); 109 110 mVolume = volume; 111 mSharedVolume = sharedVolume; 112 } 113 setReceiver(MeasurementReceiver receiver)114 public void setReceiver(MeasurementReceiver receiver) { 115 if (mReceiver == null || mReceiver.get() == null) { 116 mReceiver = new WeakReference<MeasurementReceiver>(receiver); 117 } 118 } 119 forceMeasure()120 public void forceMeasure() { 121 measure(); 122 } 123 measure()124 public void measure() { 125 new MeasureTask().execute(); 126 } 127 onDestroy()128 public void onDestroy() { 129 mReceiver = null; 130 } 131 132 private class MeasureTask extends AsyncTask<Void, Void, MeasurementDetails> { 133 @Override doInBackground(Void... params)134 protected MeasurementDetails doInBackground(Void... params) { 135 return measureExactStorage(); 136 } 137 138 @Override onPostExecute(MeasurementDetails result)139 protected void onPostExecute(MeasurementDetails result) { 140 final MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null; 141 if (receiver != null) { 142 receiver.onDetailsChanged(result); 143 } 144 } 145 } 146 measureExactStorage()147 private MeasurementDetails measureExactStorage() { 148 final List<UserInfo> users = mUser.getUsers(); 149 150 final long start = SystemClock.elapsedRealtime(); 151 152 final MeasurementDetails details = new MeasurementDetails(); 153 if (mVolume == null) return details; 154 155 if (mVolume.getType() == VolumeInfo.TYPE_PUBLIC 156 || mVolume.getType() == VolumeInfo.TYPE_STUB) { 157 details.totalSize = mVolume.getPath().getTotalSpace(); 158 details.availSize = mVolume.getPath().getUsableSpace(); 159 return details; 160 } 161 162 try { 163 details.totalSize = mStats.getTotalBytes(mVolume.fsUuid); 164 details.availSize = mStats.getFreeBytes(mVolume.fsUuid); 165 } catch (IOException e) { 166 // The storage volume became null while we were measuring it. 167 Log.w(TAG, e); 168 return details; 169 } 170 171 final long finishTotal = SystemClock.elapsedRealtime(); 172 Log.d(TAG, "Measured total storage in " + (finishTotal - start) + "ms"); 173 174 if (mSharedVolume != null && mSharedVolume.isMountedReadable()) { 175 for (UserInfo user : users) { 176 final HashMap<String, Long> mediaMap = new HashMap<>(); 177 details.mediaSize.put(user.id, mediaMap); 178 179 final ExternalStorageStats stats; 180 try { 181 stats = mStats.queryExternalStatsForUser(mSharedVolume.fsUuid, 182 UserHandle.of(user.id)); 183 } catch (IOException e) { 184 Log.w(TAG, e); 185 continue; 186 } 187 188 addValue(details.usersSize, user.id, stats.getTotalBytes()); 189 190 // Track detailed data types 191 mediaMap.put(Environment.DIRECTORY_MUSIC, stats.getAudioBytes()); 192 mediaMap.put(Environment.DIRECTORY_MOVIES, stats.getVideoBytes()); 193 mediaMap.put(Environment.DIRECTORY_PICTURES, stats.getImageBytes()); 194 195 final long miscBytes = stats.getTotalBytes() - stats.getAudioBytes() 196 - stats.getVideoBytes() - stats.getImageBytes(); 197 addValue(details.miscSize, user.id, miscBytes); 198 } 199 } 200 201 final long finishShared = SystemClock.elapsedRealtime(); 202 Log.d(TAG, "Measured shared storage in " + (finishShared - finishTotal) + "ms"); 203 204 if ((mVolume.getType() == VolumeInfo.TYPE_PRIVATE) && mVolume.isMountedReadable()) { 205 for (UserInfo user : users) { 206 final StorageStats stats; 207 try { 208 stats = mStats.queryStatsForUser(mVolume.fsUuid, UserHandle.of(user.id)); 209 } catch (IOException e) { 210 Log.w(TAG, e); 211 continue; 212 } 213 214 // Only count code once against current user 215 if (user.id == UserHandle.myUserId()) { 216 addValue(details.usersSize, user.id, stats.getAppBytes()); 217 } 218 219 addValue(details.usersSize, user.id, stats.getDataBytes()); 220 addValue(details.appsSize, user.id, stats.getAppBytes() + stats.getDataBytes()); 221 222 details.cacheSize += stats.getCacheBytes(); 223 } 224 } 225 226 final long finishPrivate = SystemClock.elapsedRealtime(); 227 Log.d(TAG, "Measured private storage in " + (finishPrivate - finishShared) + "ms"); 228 229 return details; 230 } 231 addValue(SparseLongArray array, int key, long value)232 private static void addValue(SparseLongArray array, int key, long value) { 233 array.put(key, array.get(key) + value); 234 } 235 } 236