1 /* 2 * Copyright (C) 2021 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.server.appsearch; 18 19 import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.getPackageName; 20 21 import android.annotation.NonNull; 22 import android.util.ArrayMap; 23 import android.util.Log; 24 25 import com.android.internal.annotations.VisibleForTesting; 26 import com.android.server.appsearch.external.localstorage.AppSearchImpl; 27 28 import com.google.android.icing.proto.DocumentStorageInfoProto; 29 import com.google.android.icing.proto.NamespaceStorageInfoProto; 30 import com.google.android.icing.proto.StorageInfoProto; 31 32 import java.io.File; 33 import java.io.FileInputStream; 34 import java.io.FileOutputStream; 35 import java.io.InputStream; 36 import java.util.Collections; 37 import java.util.List; 38 import java.util.Map; 39 import java.util.Objects; 40 import java.util.concurrent.locks.ReadWriteLock; 41 import java.util.concurrent.locks.ReentrantReadWriteLock; 42 43 /** Saves the storage info read from file for a user. */ 44 public final class UserStorageInfo { 45 public static final String STORAGE_INFO_FILE = "appsearch_storage"; 46 private static final String TAG = "AppSearchUserStorage"; 47 private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock(); 48 private final File mStorageInfoFile; 49 50 // Saves storage usage byte size for each package under the user, keyed by package name. 51 private Map<String, Long> mPackageStorageSizeMap; 52 // Saves storage usage byte size for all packages under the user. 53 private long mTotalStorageSizeBytes; 54 UserStorageInfo(@onNull File fileParentPath)55 public UserStorageInfo(@NonNull File fileParentPath) { 56 Objects.requireNonNull(fileParentPath); 57 mStorageInfoFile = new File(fileParentPath, STORAGE_INFO_FILE); 58 readStorageInfoFromFile(); 59 } 60 61 /** 62 * Updates storage info file with the latest storage info queried through 63 * {@link AppSearchImpl}. 64 */ updateStorageInfoFile(@onNull AppSearchImpl appSearchImpl)65 public void updateStorageInfoFile(@NonNull AppSearchImpl appSearchImpl) { 66 Objects.requireNonNull(appSearchImpl); 67 mReadWriteLock.writeLock().lock(); 68 try (FileOutputStream out = new FileOutputStream(mStorageInfoFile)) { 69 appSearchImpl.getRawStorageInfoProto().writeTo(out); 70 } catch (Throwable e) { 71 Log.w(TAG, "Failed to dump storage info into file", e); 72 } finally { 73 mReadWriteLock.writeLock().unlock(); 74 } 75 } 76 77 /** 78 * Gets storage usage byte size for a package with a given package name. 79 * 80 * <p> Please note the storage info cached in file may be stale. 81 */ getSizeBytesForPackage(@onNull String packageName)82 public long getSizeBytesForPackage(@NonNull String packageName) { 83 Objects.requireNonNull(packageName); 84 return mPackageStorageSizeMap.getOrDefault(packageName, 0L); 85 } 86 87 /** 88 * Gets total storage usage byte size for all packages under the user. 89 * 90 * <p> Please note the storage info cached in file may be stale. 91 */ getTotalSizeBytes()92 public long getTotalSizeBytes() { 93 return mTotalStorageSizeBytes; 94 } 95 96 @VisibleForTesting readStorageInfoFromFile()97 void readStorageInfoFromFile() { 98 if (mStorageInfoFile.exists()) { 99 mReadWriteLock.readLock().lock(); 100 try (InputStream in = new FileInputStream(mStorageInfoFile)) { 101 StorageInfoProto storageInfo = StorageInfoProto.parseFrom(in); 102 mTotalStorageSizeBytes = storageInfo.getTotalStorageSize(); 103 mPackageStorageSizeMap = calculatePackageStorageInfoMap(storageInfo); 104 return; 105 } catch (Throwable e) { 106 Log.w(TAG, "Failed to read storage info from file", e); 107 } finally { 108 mReadWriteLock.readLock().unlock(); 109 } 110 } 111 mTotalStorageSizeBytes = 0; 112 mPackageStorageSizeMap = Collections.emptyMap(); 113 } 114 115 /** Calculates storage usage byte size for packages from a {@link StorageInfoProto}. */ 116 // TODO(b/198553756): Storage cache effort has created two copies of the storage 117 // calculation/interpolation logic. 118 @NonNull 119 @VisibleForTesting calculatePackageStorageInfoMap(@onNull StorageInfoProto storageInfo)120 Map<String, Long> calculatePackageStorageInfoMap(@NonNull StorageInfoProto storageInfo) { 121 Map<String, Long> packageStorageInfoMap = new ArrayMap<>(); 122 if (storageInfo.hasDocumentStorageInfo()) { 123 DocumentStorageInfoProto documentStorageInfo = storageInfo.getDocumentStorageInfo(); 124 List<NamespaceStorageInfoProto> namespaceStorageInfoList = 125 documentStorageInfo.getNamespaceStorageInfoList(); 126 127 Map<String, Integer> packageDocumentCountMap = new ArrayMap<>(); 128 long totalDocuments = 0; 129 for (int i = 0; i < namespaceStorageInfoList.size(); i++) { 130 NamespaceStorageInfoProto namespaceStorageInfo = namespaceStorageInfoList.get(i); 131 String packageName = getPackageName(namespaceStorageInfo.getNamespace()); 132 int namespaceDocuments = namespaceStorageInfo.getNumAliveDocuments() 133 + namespaceStorageInfo.getNumExpiredDocuments(); 134 totalDocuments += namespaceDocuments; 135 packageDocumentCountMap.put(packageName, 136 packageDocumentCountMap.getOrDefault(packageName, 0) 137 + namespaceDocuments); 138 } 139 140 long totalStorageSize = storageInfo.getTotalStorageSize(); 141 for (Map.Entry<String, Integer> entry : packageDocumentCountMap.entrySet()) { 142 // Since we don't have the exact size of all the documents, we do an estimation. 143 // Note that while the total storage takes into account schema, index, etc. in 144 // addition to documents, we'll only calculate the percentage based on number of 145 // documents under packages. 146 packageStorageInfoMap.put(entry.getKey(), 147 (long) (entry.getValue() * 1.0 / totalDocuments * totalStorageSize)); 148 } 149 } 150 return Collections.unmodifiableMap(packageStorageInfoMap); 151 } 152 } 153