package android.ext.services.storage;
import android.app.usage.CacheQuotaHint;
import android.app.usage.CacheQuotaService;
import android.os.Environment;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.util.ArrayMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class CacheQuotaServiceImpl extends CacheQuotaService {
private static final double CACHE_RESERVE_RATIO = 0.15;
@Override
public List<CacheQuotaHint> onComputeCacheQuotaHints(List<CacheQuotaHint> requests) {
ArrayMap<String, List<CacheQuotaHint>> byUuid = new ArrayMap<>();
final int requestCount = requests.size();
for (int i = 0; i < requestCount; i++) {
CacheQuotaHint request = requests.get(i);
String uuid = request.getVolumeUuid();
List<CacheQuotaHint> listForUuid = byUuid.get(uuid);
if (listForUuid == null) {
listForUuid = new ArrayList<>();
byUuid.put(uuid, listForUuid);
}
listForUuid.add(request);
}
List<CacheQuotaHint> processed = new ArrayList<>();
byUuid.entrySet().forEach(
requestListEntry -> {
Map<Integer, List<CacheQuotaHint>> byUid = requestListEntry.getValue()
.stream()
.collect(Collectors.groupingBy(CacheQuotaHint::getUid));
byUid.values().forEach(uidGroupedList -> {
int size = uidGroupedList.size();
if (size < 2) {
return;
}
CacheQuotaHint first = uidGroupedList.get(0);
for (int i = 1; i < size; i++) {
first.getUsageStats().mTotalTimeInForeground +=
uidGroupedList.get(i).getUsageStats().mTotalTimeInForeground;
}
});
List<CacheQuotaHint> flattenedRequests =
byUid.values()
.stream()
.map(entryList -> entryList.get(0))
.filter(entry -> entry.getUsageStats().mTotalTimeInForeground != 0)
.sorted(sCacheQuotaRequestComparator)
.collect(Collectors.toList());
double sum = getSumOfFairShares(flattenedRequests.size());
String uuid = requestListEntry.getKey();
long reservedSize = getReservedCacheSize(uuid);
for (int count = 0; count < flattenedRequests.size(); count++) {
double share = getFairShareForPosition(count) / sum;
CacheQuotaHint entry = flattenedRequests.get(count);
CacheQuotaHint.Builder builder = new CacheQuotaHint.Builder(entry);
builder.setQuota(Math.round(share * reservedSize));
processed.add(builder.build());
}
}
);
return processed.stream()
.filter(request -> request.getQuota() > 0).collect(Collectors.toList());
}
private double getFairShareForPosition(int position) {
double value = 1.0 / Math.log(position + 3) - 0.285;
return (value > 0.01) ? value : 0.01;
}
private double getSumOfFairShares(int size) {
double sum = 0;
for (int i = 0; i < size; i++) {
sum += getFairShareForPosition(i);
}
return sum;
}
private long getReservedCacheSize(String uuid) {
StorageManager storageManager = getSystemService(StorageManager.class);
long freeBytes = 0;
if (uuid == StorageManager.UUID_PRIVATE_INTERNAL) {
freeBytes = Environment.getDataDirectory().getUsableSpace();
} else {
final VolumeInfo vol = storageManager.findVolumeByUuid(uuid);
freeBytes = vol.getPath().getUsableSpace();
}
return Math.round(freeBytes * CACHE_RESERVE_RATIO);
}
private static Comparator<CacheQuotaHint> sCacheQuotaRequestComparator =
new Comparator<CacheQuotaHint>() {
@Override
public int compare(CacheQuotaHint o, CacheQuotaHint t1) {
long x = t1.getUsageStats().getTotalTimeInForeground();
long y = o.getUsageStats().getTotalTimeInForeground();
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
};
}