• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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