blob: d6e8598503e7c1ba553d21971145073f47595ea5 [file] [log] [blame]
/*
* Copyright (C) 2021 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.server.appsearch;
import static com.android.server.appsearch.external.localstorage.util.PrefixUtil.getPackageName;
import android.annotation.NonNull;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.appsearch.external.localstorage.AppSearchImpl;
import com.google.android.icing.proto.DocumentStorageInfoProto;
import com.google.android.icing.proto.NamespaceStorageInfoProto;
import com.google.android.icing.proto.StorageInfoProto;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/** Saves the storage info read from file for a user. */
public final class UserStorageInfo {
public static final String STORAGE_INFO_FILE = "appsearch_storage";
private static final String TAG = "AppSearchUserStorage";
private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();
private final File mStorageInfoFile;
// Saves storage usage byte size for each package under the user, keyed by package name.
private Map<String, Long> mPackageStorageSizeMap;
// Saves storage usage byte size for all packages under the user.
private long mTotalStorageSizeBytes;
public UserStorageInfo(@NonNull File fileParentPath) {
Objects.requireNonNull(fileParentPath);
mStorageInfoFile = new File(fileParentPath, STORAGE_INFO_FILE);
readStorageInfoFromFile();
}
/**
* Updates storage info file with the latest storage info queried through
* {@link AppSearchImpl}.
*/
public void updateStorageInfoFile(@NonNull AppSearchImpl appSearchImpl) {
Objects.requireNonNull(appSearchImpl);
mReadWriteLock.writeLock().lock();
try (FileOutputStream out = new FileOutputStream(mStorageInfoFile)) {
appSearchImpl.getRawStorageInfoProto().writeTo(out);
} catch (Throwable e) {
Log.w(TAG, "Failed to dump storage info into file", e);
} finally {
mReadWriteLock.writeLock().unlock();
}
}
/**
* Gets storage usage byte size for a package with a given package name.
*
* <p> Please note the storage info cached in file may be stale.
*/
public long getSizeBytesForPackage(@NonNull String packageName) {
Objects.requireNonNull(packageName);
return mPackageStorageSizeMap.getOrDefault(packageName, 0L);
}
/**
* Gets total storage usage byte size for all packages under the user.
*
* <p> Please note the storage info cached in file may be stale.
*/
public long getTotalSizeBytes() {
return mTotalStorageSizeBytes;
}
@VisibleForTesting
void readStorageInfoFromFile() {
if (mStorageInfoFile.exists()) {
mReadWriteLock.readLock().lock();
try (InputStream in = new FileInputStream(mStorageInfoFile)) {
StorageInfoProto storageInfo = StorageInfoProto.parseFrom(in);
mTotalStorageSizeBytes = storageInfo.getTotalStorageSize();
mPackageStorageSizeMap = calculatePackageStorageInfoMap(storageInfo);
return;
} catch (Throwable e) {
Log.w(TAG, "Failed to read storage info from file", e);
} finally {
mReadWriteLock.readLock().unlock();
}
}
mTotalStorageSizeBytes = 0;
mPackageStorageSizeMap = Collections.emptyMap();
}
/** Calculates storage usage byte size for packages from a {@link StorageInfoProto}. */
// TODO(b/198553756): Storage cache effort has created two copies of the storage
// calculation/interpolation logic.
@NonNull
@VisibleForTesting
Map<String, Long> calculatePackageStorageInfoMap(@NonNull StorageInfoProto storageInfo) {
Map<String, Long> packageStorageInfoMap = new ArrayMap<>();
if (storageInfo.hasDocumentStorageInfo()) {
DocumentStorageInfoProto documentStorageInfo = storageInfo.getDocumentStorageInfo();
List<NamespaceStorageInfoProto> namespaceStorageInfoList =
documentStorageInfo.getNamespaceStorageInfoList();
Map<String, Integer> packageDocumentCountMap = new ArrayMap<>();
long totalDocuments = 0;
for (int i = 0; i < namespaceStorageInfoList.size(); i++) {
NamespaceStorageInfoProto namespaceStorageInfo = namespaceStorageInfoList.get(i);
String packageName = getPackageName(namespaceStorageInfo.getNamespace());
int namespaceDocuments = namespaceStorageInfo.getNumAliveDocuments()
+ namespaceStorageInfo.getNumExpiredDocuments();
totalDocuments += namespaceDocuments;
packageDocumentCountMap.put(packageName,
packageDocumentCountMap.getOrDefault(packageName, 0)
+ namespaceDocuments);
}
long totalStorageSize = storageInfo.getTotalStorageSize();
for (Map.Entry<String, Integer> entry : packageDocumentCountMap.entrySet()) {
// Since we don't have the exact size of all the documents, we do an estimation.
// Note that while the total storage takes into account schema, index, etc. in
// addition to documents, we'll only calculate the percentage based on number of
// documents under packages.
packageStorageInfoMap.put(entry.getKey(),
(long) (entry.getValue() * 1.0 / totalDocuments * totalStorageSize));
}
}
return Collections.unmodifiableMap(packageStorageInfoMap);
}
}