CacheQuotaServiceImpl.java revision cf9d19e030830fd808d59f1c97edf65e66f675d6
1 2/* 3 * Copyright (C) 2017 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package android.ext.services.storage; 19 20import android.app.usage.CacheQuotaHint; 21import android.app.usage.CacheQuotaService; 22import android.os.Environment; 23import android.os.storage.StorageManager; 24import android.os.storage.VolumeInfo; 25import android.util.ArrayMap; 26 27import java.util.ArrayList; 28import java.util.Comparator; 29import java.util.List; 30import java.util.Map; 31import java.util.stream.Collectors; 32 33/** 34 * CacheQuotaServiceImpl implements the CacheQuotaService with a strategy for populating the quota 35 * of {@link CacheQuotaHint}. 36 */ 37public class CacheQuotaServiceImpl extends CacheQuotaService { 38 private static final double CACHE_RESERVE_RATIO = 0.15; 39 40 @Override 41 public List<CacheQuotaHint> onComputeCacheQuotaHints(List<CacheQuotaHint> requests) { 42 ArrayMap<String, List<CacheQuotaHint>> byUuid = new ArrayMap<>(); 43 final int requestCount = requests.size(); 44 for (int i = 0; i < requestCount; i++) { 45 CacheQuotaHint request = requests.get(i); 46 String uuid = request.getVolumeUuid(); 47 List<CacheQuotaHint> listForUuid = byUuid.get(uuid); 48 if (listForUuid == null) { 49 listForUuid = new ArrayList<>(); 50 byUuid.put(uuid, listForUuid); 51 } 52 listForUuid.add(request); 53 } 54 55 List<CacheQuotaHint> processed = new ArrayList<>(); 56 byUuid.entrySet().forEach( 57 requestListEntry -> { 58 // Collapse all usage stats to the same uid. 59 Map<Integer, List<CacheQuotaHint>> byUid = requestListEntry.getValue() 60 .stream() 61 .collect(Collectors.groupingBy(CacheQuotaHint::getUid)); 62 byUid.values().forEach(uidGroupedList -> { 63 int size = uidGroupedList.size(); 64 if (size < 2) { 65 return; 66 } 67 CacheQuotaHint first = uidGroupedList.get(0); 68 for (int i = 1; i < size; i++) { 69 /* Note: We can't use the UsageStats built-in addition function because 70 UIDs may span multiple packages and usage stats adding has 71 matching package names as a precondition. */ 72 first.getUsageStats().mTotalTimeInForeground += 73 uidGroupedList.get(i).getUsageStats().mTotalTimeInForeground; 74 } 75 }); 76 77 // Because the foreground stats have been added to the first element, we need 78 // a list of only the first values (which contain the merged foreground time). 79 List<CacheQuotaHint> flattenedRequests = 80 byUid.values() 81 .stream() 82 .map(entryList -> entryList.get(0)) 83 .filter(entry -> entry.getUsageStats().mTotalTimeInForeground != 0) 84 .sorted(sCacheQuotaRequestComparator) 85 .collect(Collectors.toList()); 86 87 // Because the elements are sorted, we can use the index to also be the sorted 88 // index for cache quota calculation. 89 double sum = getSumOfFairShares(flattenedRequests.size()); 90 String uuid = requestListEntry.getKey(); 91 long reservedSize = getReservedCacheSize(uuid); 92 for (int count = 0; count < flattenedRequests.size(); count++) { 93 double share = getFairShareForPosition(count) / sum; 94 CacheQuotaHint entry = flattenedRequests.get(count); 95 CacheQuotaHint.Builder builder = new CacheQuotaHint.Builder(entry); 96 builder.setQuota(Math.round(share * reservedSize)); 97 processed.add(builder.build()); 98 } 99 } 100 ); 101 102 return processed.stream() 103 .filter(request -> request.getQuota() > 0).collect(Collectors.toList()); 104 } 105 106 private double getFairShareForPosition(int position) { 107 double value = 1.0 / Math.log(position + 3) - 0.285; 108 return (value > 0.01) ? value : 0.01; 109 } 110 111 private double getSumOfFairShares(int size) { 112 double sum = 0; 113 for (int i = 0; i < size; i++) { 114 sum += getFairShareForPosition(i); 115 } 116 return sum; 117 } 118 119 private long getReservedCacheSize(String uuid) { 120 // TODO: Revisit the cache size after running more storage tests. 121 // TODO: Figure out how to ensure ExtServices has the permissions to call 122 // StorageStatsManager, because this is ignoring the cache... 123 StorageManager storageManager = getSystemService(StorageManager.class); 124 long freeBytes = 0; 125 if (uuid == StorageManager.UUID_PRIVATE_INTERNAL) { // regular equals because of null 126 freeBytes = Environment.getDataDirectory().getFreeSpace(); 127 } else { 128 final VolumeInfo vol = storageManager.findVolumeByUuid(uuid); 129 freeBytes = vol.getPath().getFreeSpace(); 130 } 131 return Math.round(freeBytes * CACHE_RESERVE_RATIO); 132 } 133 134 // Compares based upon foreground time. 135 private static Comparator<CacheQuotaHint> sCacheQuotaRequestComparator = 136 new Comparator<CacheQuotaHint>() { 137 @Override 138 public int compare(CacheQuotaHint o, CacheQuotaHint t1) { 139 long x = t1.getUsageStats().getTotalTimeInForeground(); 140 long y = o.getUsageStats().getTotalTimeInForeground(); 141 return (x < y) ? -1 : ((x == y) ? 0 : 1); 142 } 143 }; 144} 145