/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settingslib.deviceinfo;
import android.app.usage.ExternalStorageStats;
import android.app.usage.StorageStats;
import android.app.usage.StorageStatsManager;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageVolume;
import android.os.storage.VolumeInfo;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseLongArray;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.List;
/**
* Utility for measuring the disk usage of internal storage or a physical
* {@link StorageVolume}.
*/
public class StorageMeasurement {
private static final String TAG = "StorageMeasurement";
public static class MeasurementDetails {
/** Size of storage device. */
public long totalSize;
/** Size of available space. */
public long availSize;
/** Size of all cached data. */
public long cacheSize;
/**
* Total disk space used by everything.
*
* Key is {@link UserHandle}.
*/
public SparseLongArray usersSize = new SparseLongArray();
/**
* Total disk space used by apps.
*
* Key is {@link UserHandle}.
*/
public SparseLongArray appsSize = new SparseLongArray();
/**
* Total disk space used by media on shared storage.
*
* First key is {@link UserHandle}. Second key is media type, such as
* {@link Environment#DIRECTORY_PICTURES}.
*/
public SparseArray> mediaSize = new SparseArray<>();
/**
* Total disk space used by non-media on shared storage.
*
* Key is {@link UserHandle}.
*/
public SparseLongArray miscSize = new SparseLongArray();
@Override
public String toString() {
return "MeasurementDetails: [totalSize: " + totalSize + " availSize: " + availSize
+ " cacheSize: " + cacheSize + " mediaSize: " + mediaSize
+ " miscSize: " + miscSize + "usersSize: " + usersSize + "]";
}
}
public interface MeasurementReceiver {
void onDetailsChanged(MeasurementDetails details);
}
private WeakReference mReceiver;
private final Context mContext;
private final UserManager mUser;
private final StorageStatsManager mStats;
private final VolumeInfo mVolume;
private final VolumeInfo mSharedVolume;
public StorageMeasurement(Context context, VolumeInfo volume, VolumeInfo sharedVolume) {
mContext = context.getApplicationContext();
mUser = mContext.getSystemService(UserManager.class);
mStats = mContext.getSystemService(StorageStatsManager.class);
mVolume = volume;
mSharedVolume = sharedVolume;
}
public void setReceiver(MeasurementReceiver receiver) {
if (mReceiver == null || mReceiver.get() == null) {
mReceiver = new WeakReference(receiver);
}
}
public void forceMeasure() {
measure();
}
public void measure() {
new MeasureTask().execute();
}
public void onDestroy() {
mReceiver = null;
}
private class MeasureTask extends AsyncTask {
@Override
protected MeasurementDetails doInBackground(Void... params) {
return measureExactStorage();
}
@Override
protected void onPostExecute(MeasurementDetails result) {
final MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null;
if (receiver != null) {
receiver.onDetailsChanged(result);
}
}
}
private MeasurementDetails measureExactStorage() {
final List users = mUser.getUsers();
final long start = SystemClock.elapsedRealtime();
final MeasurementDetails details = new MeasurementDetails();
if (mVolume == null) return details;
if (mVolume.getType() == VolumeInfo.TYPE_PUBLIC) {
details.totalSize = mVolume.getPath().getTotalSpace();
details.availSize = mVolume.getPath().getUsableSpace();
return details;
}
try {
details.totalSize = mStats.getTotalBytes(mVolume.fsUuid);
details.availSize = mStats.getFreeBytes(mVolume.fsUuid);
} catch (IOException e) {
// The storage volume became null while we were measuring it.
Log.w(TAG, e);
return details;
}
final long finishTotal = SystemClock.elapsedRealtime();
Log.d(TAG, "Measured total storage in " + (finishTotal - start) + "ms");
if (mSharedVolume != null && mSharedVolume.isMountedReadable()) {
for (UserInfo user : users) {
final HashMap mediaMap = new HashMap<>();
details.mediaSize.put(user.id, mediaMap);
final ExternalStorageStats stats;
try {
stats = mStats.queryExternalStatsForUser(mSharedVolume.fsUuid,
UserHandle.of(user.id));
} catch (IOException e) {
Log.w(TAG, e);
continue;
}
addValue(details.usersSize, user.id, stats.getTotalBytes());
// Track detailed data types
mediaMap.put(Environment.DIRECTORY_MUSIC, stats.getAudioBytes());
mediaMap.put(Environment.DIRECTORY_MOVIES, stats.getVideoBytes());
mediaMap.put(Environment.DIRECTORY_PICTURES, stats.getImageBytes());
final long miscBytes = stats.getTotalBytes() - stats.getAudioBytes()
- stats.getVideoBytes() - stats.getImageBytes();
addValue(details.miscSize, user.id, miscBytes);
}
}
final long finishShared = SystemClock.elapsedRealtime();
Log.d(TAG, "Measured shared storage in " + (finishShared - finishTotal) + "ms");
if ((mVolume.getType() == VolumeInfo.TYPE_PRIVATE) && mVolume.isMountedReadable()) {
for (UserInfo user : users) {
final StorageStats stats;
try {
stats = mStats.queryStatsForUser(mVolume.fsUuid, UserHandle.of(user.id));
} catch (IOException e) {
Log.w(TAG, e);
continue;
}
// Only count code once against current user
if (user.id == UserHandle.myUserId()) {
addValue(details.usersSize, user.id, stats.getCodeBytes());
}
addValue(details.usersSize, user.id, stats.getDataBytes());
addValue(details.appsSize, user.id, stats.getCodeBytes() + stats.getDataBytes());
details.cacheSize += stats.getCacheBytes();
}
}
final long finishPrivate = SystemClock.elapsedRealtime();
Log.d(TAG, "Measured private storage in " + (finishPrivate - finishShared) + "ms");
return details;
}
private static void addValue(SparseLongArray array, int key, long value) {
array.put(key, array.get(key) + value);
}
}