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