• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 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 package com.android.server.appsearch.external.localstorage.visibilitystore;
17 
18 import static android.app.appsearch.AppSearchResult.RESULT_NOT_FOUND;
19 
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.app.appsearch.AppSearchResult;
23 import android.app.appsearch.AppSearchSchema;
24 import android.app.appsearch.GenericDocument;
25 import android.app.appsearch.GetSchemaResponse;
26 import android.app.appsearch.InternalSetSchemaResponse;
27 import android.app.appsearch.VisibilityDocument;
28 import android.app.appsearch.VisibilityPermissionDocument;
29 import android.app.appsearch.exceptions.AppSearchException;
30 import android.util.ArrayMap;
31 import android.util.Log;
32 
33 import com.android.server.appsearch.external.localstorage.AppSearchImpl;
34 import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
35 
36 import com.google.android.icing.proto.PersistType;
37 
38 import java.util.Arrays;
39 import java.util.Collections;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.Objects;
43 import java.util.Set;
44 
45 /**
46  * Stores all visibility settings for all databases that AppSearchImpl knows about. Persists the
47  * visibility settings and reloads them on initialization.
48  *
49  * <p>The VisibilityStore creates a {@link VisibilityDocument} for each schema. This document holds
50  * the visibility settings that apply to that schema. The VisibilityStore also creates a schema for
51  * these documents and has its own package and database so that its data doesn't interfere with any
52  * clients' data. It persists the document and schema through AppSearchImpl.
53  *
54  * <p>These visibility settings won't be used in AppSearch Jetpack, we only store them for clients
55  * to look up.
56  *
57  * @hide
58  */
59 public class VisibilityStore {
60     private static final String TAG = "AppSearchVisibilityStor";
61     /**
62      * These cannot have any of the special characters used by AppSearchImpl (e.g. {@code
63      * AppSearchImpl#PACKAGE_DELIMITER} or {@code AppSearchImpl#DATABASE_DELIMITER}.
64      */
65     public static final String VISIBILITY_PACKAGE_NAME = "VS#Pkg";
66 
67     public static final String VISIBILITY_DATABASE_NAME = "VS#Db";
68 
69     /**
70      * Map of PrefixedSchemaType and VisibilityDocument stores visibility information for each
71      * schema type.
72      */
73     private final Map<String, VisibilityDocument> mVisibilityDocumentMap = new ArrayMap<>();
74 
75     private final AppSearchImpl mAppSearchImpl;
76 
VisibilityStore(@onNull AppSearchImpl appSearchImpl)77     public VisibilityStore(@NonNull AppSearchImpl appSearchImpl) throws AppSearchException {
78         mAppSearchImpl = Objects.requireNonNull(appSearchImpl);
79 
80         GetSchemaResponse getSchemaResponse =
81                 mAppSearchImpl.getSchema(
82                         VISIBILITY_PACKAGE_NAME,
83                         VISIBILITY_DATABASE_NAME,
84                         new CallerAccess(/*callingPackageName=*/ VISIBILITY_PACKAGE_NAME));
85         List<VisibilityDocumentV1> visibilityDocumentsV1s = null;
86         switch (getSchemaResponse.getVersion()) {
87             case VisibilityDocument.SCHEMA_VERSION_DOC_PER_PACKAGE:
88                 // TODO (b/202194495) add VisibilityDocument in version 0 back instead of using
89                 //  GenericDocument.
90                 List<GenericDocument> visibilityDocumentsV0s =
91                         VisibilityStoreMigrationHelperFromV0.getVisibilityDocumentsInVersion0(
92                                 getSchemaResponse, mAppSearchImpl);
93                 visibilityDocumentsV1s =
94                         VisibilityStoreMigrationHelperFromV0.toVisibilityDocumentV1(
95                                 visibilityDocumentsV0s);
96                 // fall through
97             case VisibilityDocument.SCHEMA_VERSION_DOC_PER_SCHEMA:
98                 if (visibilityDocumentsV1s == null) {
99                     // We need to read VisibilityDocument in Version 1 from AppSearch instead of
100                     // taking from the above step.
101                     visibilityDocumentsV1s =
102                             VisibilityStoreMigrationHelperFromV1.getVisibilityDocumentsInVersion1(
103                                     mAppSearchImpl);
104                 }
105                 setLatestSchemaAndDocuments(
106                         VisibilityStoreMigrationHelperFromV1.toVisibilityDocumentsV2(
107                                 visibilityDocumentsV1s));
108                 break;
109             case VisibilityDocument.SCHEMA_VERSION_LATEST:
110                 Set<AppSearchSchema> existingVisibilitySchema = getSchemaResponse.getSchemas();
111                 if (existingVisibilitySchema.contains(VisibilityDocument.SCHEMA)
112                         && existingVisibilitySchema.contains(VisibilityPermissionDocument.SCHEMA)) {
113                     // The latest Visibility schema is in AppSearch, we must find our schema type.
114                     // Extract all stored Visibility Document into mVisibilityDocumentMap.
115                     loadVisibilityDocumentMap();
116                 } else {
117                     // We must have a broken schema. Reset it to the latest version.
118                     // Do NOT set forceOverride to be true here, see comment below.
119                     InternalSetSchemaResponse internalSetSchemaResponse =
120                             mAppSearchImpl.setSchema(
121                                     VISIBILITY_PACKAGE_NAME,
122                                     VISIBILITY_DATABASE_NAME,
123                                     Arrays.asList(
124                                             VisibilityDocument.SCHEMA,
125                                             VisibilityPermissionDocument.SCHEMA),
126                                     /*visibilityDocuments=*/ Collections.emptyList(),
127                                     /*forceOverride=*/ false,
128                                     /*version=*/ VisibilityDocument.SCHEMA_VERSION_LATEST,
129                                     /*setSchemaStatsBuilder=*/ null);
130                     if (!internalSetSchemaResponse.isSuccess()) {
131                         // If you hit problem here it means you made a incompatible change in
132                         // Visibility Schema without update the version number. You should bump
133                         // the version number and create a VisibilityStoreMigrationHelper which
134                         // can analyse the different between the old version and the new version
135                         // to migration user's visibility settings.
136                         throw new AppSearchException(
137                                 AppSearchResult.RESULT_INTERNAL_ERROR,
138                                 "Fail to set the latest visibility schema to AppSearch. "
139                                         + "You may need to update the visibility schema version "
140                                         + "number.");
141                     }
142                 }
143                 break;
144             default:
145                 // We must did something wrong.
146                 throw new AppSearchException(
147                         AppSearchResult.RESULT_INTERNAL_ERROR,
148                         "Found unsupported visibility version: " + getSchemaResponse.getVersion());
149         }
150     }
151 
152     /**
153      * Sets visibility settings for the given {@link VisibilityDocument}s. Any previous {@link
154      * VisibilityDocument}s with same prefixed schema type will be overwritten.
155      *
156      * @param prefixedVisibilityDocuments List of prefixed {@link VisibilityDocument} which contains
157      *     schema type's visibility information.
158      * @throws AppSearchException on AppSearchImpl error.
159      */
setVisibility(@onNull List<VisibilityDocument> prefixedVisibilityDocuments)160     public void setVisibility(@NonNull List<VisibilityDocument> prefixedVisibilityDocuments)
161             throws AppSearchException {
162         Objects.requireNonNull(prefixedVisibilityDocuments);
163         // Save new setting.
164         for (int i = 0; i < prefixedVisibilityDocuments.size(); i++) {
165             // put VisibilityDocument to AppSearchImpl and mVisibilityDocumentMap. If there is a
166             // VisibilityDocument with same prefixed schema exists, it will be replaced by new
167             // VisibilityDocument in both AppSearch and memory look up map.
168             VisibilityDocument prefixedVisibilityDocument = prefixedVisibilityDocuments.get(i);
169             mAppSearchImpl.putDocument(
170                     VISIBILITY_PACKAGE_NAME,
171                     VISIBILITY_DATABASE_NAME,
172                     prefixedVisibilityDocument,
173                     /*sendChangeNotifications=*/ false,
174                     /*logger=*/ null);
175             mVisibilityDocumentMap.put(
176                     prefixedVisibilityDocument.getId(), prefixedVisibilityDocument);
177         }
178         // Now that the visibility document has been written. Persist the newly written data.
179         mAppSearchImpl.persistToDisk(PersistType.Code.LITE);
180     }
181 
182     /**
183      * Remove the visibility setting for the given prefixed schema type from both AppSearch and
184      * memory look up map.
185      */
removeVisibility(@onNull Set<String> prefixedSchemaTypes)186     public void removeVisibility(@NonNull Set<String> prefixedSchemaTypes)
187             throws AppSearchException {
188         for (String prefixedSchemaType : prefixedSchemaTypes) {
189             if (mVisibilityDocumentMap.remove(prefixedSchemaType) != null) {
190                 // The deleted schema is not all-default setting, we need to remove its
191                 // VisibilityDocument from Icing.
192                 try {
193                     mAppSearchImpl.remove(
194                             VISIBILITY_PACKAGE_NAME,
195                             VISIBILITY_DATABASE_NAME,
196                             VisibilityDocument.NAMESPACE,
197                             prefixedSchemaType,
198                             /*removeStatsBuilder=*/ null);
199                 } catch (AppSearchException e) {
200                     if (e.getResultCode() == RESULT_NOT_FOUND) {
201                         // We are trying to remove this visibility setting, so it's weird but seems
202                         // to be fine if we cannot find it.
203                         Log.e(
204                                 TAG,
205                                 "Cannot find visibility document for "
206                                         + prefixedSchemaType
207                                         + " to remove.");
208                         return;
209                     }
210                     throw e;
211                 }
212             }
213         }
214     }
215 
216     /** Gets the {@link VisibilityDocument} for the given prefixed schema type. */
217     @Nullable
getVisibility(@onNull String prefixedSchemaType)218     public VisibilityDocument getVisibility(@NonNull String prefixedSchemaType) {
219         return mVisibilityDocumentMap.get(prefixedSchemaType);
220     }
221 
222     /**
223      * Loads all stored latest {@link VisibilityDocument} from Icing, and put them into {@link
224      * #mVisibilityDocumentMap}.
225      */
loadVisibilityDocumentMap()226     private void loadVisibilityDocumentMap() throws AppSearchException {
227         // Populate visibility settings set
228         List<String> cachedSchemaTypes = mAppSearchImpl.getAllPrefixedSchemaTypes();
229         for (int i = 0; i < cachedSchemaTypes.size(); i++) {
230             String prefixedSchemaType = cachedSchemaTypes.get(i);
231             String packageName = PrefixUtil.getPackageName(prefixedSchemaType);
232             if (packageName.equals(VISIBILITY_PACKAGE_NAME)) {
233                 continue; // Our own package. Skip.
234             }
235 
236             VisibilityDocument visibilityDocument;
237             try {
238                 // Note: We use the other clients' prefixed schema type as ids
239                 visibilityDocument =
240                         new VisibilityDocument(
241                                 mAppSearchImpl.getDocument(
242                                         VISIBILITY_PACKAGE_NAME,
243                                         VISIBILITY_DATABASE_NAME,
244                                         VisibilityDocument.NAMESPACE,
245                                         /*id=*/ prefixedSchemaType,
246                                         /*typePropertyPaths=*/ Collections.emptyMap()));
247             } catch (AppSearchException e) {
248                 if (e.getResultCode() == RESULT_NOT_FOUND) {
249                     // The schema has all default setting and we won't have a VisibilityDocument for
250                     // it.
251                     continue;
252                 }
253                 // Otherwise, this is some other error we should pass up.
254                 throw e;
255             }
256             mVisibilityDocumentMap.put(prefixedSchemaType, visibilityDocument);
257         }
258     }
259 
260     /** Set the latest version of {@link VisibilityDocument} and its schema to AppSearch. */
setLatestSchemaAndDocuments(@onNull List<VisibilityDocument> migratedDocuments)261     private void setLatestSchemaAndDocuments(@NonNull List<VisibilityDocument> migratedDocuments)
262             throws AppSearchException {
263         // The latest schema type doesn't exist yet. Add it. Set forceOverride true to
264         // delete old schema.
265         InternalSetSchemaResponse internalSetSchemaResponse =
266                 mAppSearchImpl.setSchema(
267                         VISIBILITY_PACKAGE_NAME,
268                         VISIBILITY_DATABASE_NAME,
269                         Arrays.asList(
270                                 VisibilityDocument.SCHEMA, VisibilityPermissionDocument.SCHEMA),
271                         /*visibilityDocuments=*/ Collections.emptyList(),
272                         /*forceOverride=*/ true,
273                         /*version=*/ VisibilityDocument.SCHEMA_VERSION_LATEST,
274                         /*setSchemaStatsBuilder=*/ null);
275         if (!internalSetSchemaResponse.isSuccess()) {
276             // Impossible case, we just set forceOverride to be true, we should never
277             // fail in incompatible changes.
278             throw new AppSearchException(
279                     AppSearchResult.RESULT_INTERNAL_ERROR,
280                     internalSetSchemaResponse.getErrorMessage());
281         }
282         for (int i = 0; i < migratedDocuments.size(); i++) {
283             VisibilityDocument migratedDocument = migratedDocuments.get(i);
284             mVisibilityDocumentMap.put(migratedDocument.getId(), migratedDocument);
285             mAppSearchImpl.putDocument(
286                     VISIBILITY_PACKAGE_NAME,
287                     VISIBILITY_DATABASE_NAME,
288                     migratedDocument,
289                     /*sendChangeNotifications=*/ false,
290                     /*logger=*/ null);
291         }
292     }
293 }
294