1 /*
2  * Copyright 2022 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.external.localstorage.visibilitystore;
18 
19 import android.annotation.NonNull;
20 import android.app.appsearch.AppSearchResult;
21 import android.app.appsearch.AppSearchSchema;
22 import android.app.appsearch.GenericDocument;
23 import android.app.appsearch.GetSchemaResponse;
24 import android.app.appsearch.PackageIdentifier;
25 import android.app.appsearch.exceptions.AppSearchException;
26 import android.util.ArrayMap;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 import com.android.server.appsearch.external.localstorage.AppSearchImpl;
30 import com.android.server.appsearch.external.localstorage.util.PrefixUtil;
31 
32 import java.util.ArrayList;
33 import java.util.Collections;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Objects;
37 import java.util.Set;
38 
39 /**
40  * The helper class to store Visibility Document information of version 0 and handle the upgrade to
41  * version 1.
42  *
43  * @hide
44  */
45 public class VisibilityStoreMigrationHelperFromV0 {
VisibilityStoreMigrationHelperFromV0()46     private VisibilityStoreMigrationHelperFromV0() {}
47 
48     /** Prefix to add to all visibility document ids. IcingSearchEngine doesn't allow empty ids. */
49     private static final String DEPRECATED_ID_PREFIX = "uri:";
50 
51     /** Schema type for documents that hold AppSearch's metadata, e.g. visibility settings */
52     @VisibleForTesting static final String DEPRECATED_VISIBILITY_SCHEMA_TYPE = "VisibilityType";
53 
54     /**
55      * Property that holds the list of platform-hidden schemas, as part of the visibility settings.
56      */
57     @VisibleForTesting
58     static final String DEPRECATED_NOT_DISPLAYED_BY_SYSTEM_PROPERTY = "notPlatformSurfaceable";
59 
60     /** Property that holds nested documents of package accessible schemas. */
61     @VisibleForTesting
62     static final String DEPRECATED_VISIBLE_TO_PACKAGES_PROPERTY = "packageAccessible";
63 
64     /**
65      * Property that holds the list of platform-hidden schemas, as part of the visibility settings.
66      */
67     @VisibleForTesting static final String DEPRECATED_PACKAGE_SCHEMA_TYPE = "PackageAccessibleType";
68 
69     /** Property that holds the prefixed schema type that is accessible by some package. */
70     @VisibleForTesting
71     static final String DEPRECATED_ACCESSIBLE_SCHEMA_PROPERTY = "accessibleSchema";
72 
73     /** Property that holds the package name that can access a schema. */
74     @VisibleForTesting static final String DEPRECATED_PACKAGE_NAME_PROPERTY = "packageName";
75 
76     /** Property that holds the SHA 256 certificate of the app that can access a schema. */
77     @VisibleForTesting static final String DEPRECATED_SHA_256_CERT_PROPERTY = "sha256Cert";
78 
79     //    The visibility schema of version 0.
80     // ---------------------------------------------------------------------------------------------
81     //    Schema of DEPRECATED_VISIBILITY_SCHEMA_TYPE:
82     //    new AppSearchSchema.Builder(
83     //            DEPRECATED_VISIBILITY_SCHEMA_TYPE)
84     //            .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
85     //                    DEPRECATED_NOT_DISPLAYED_BY_SYSTEM_PROPERTY)
86     //                    .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
87     //                    .build())
88     //            .addProperty(new AppSearchSchema.DocumentPropertyConfig.Builder(
89     //                    DEPRECATED_VISIBLE_TO_PACKAGES_PROPERTY,
90     //                    DEPRECATED_PACKAGE_SCHEMA_TYPE)
91     //                    .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
92     //                    .build())
93     //            .build();
94     //    Schema of DEPRECATED_PACKAGE_SCHEMA_TYPE:
95     //    new AppSearchSchema.Builder(DEPRECATED_PACKAGE_SCHEMA_TYPE)
96     //        .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
97     //                 DEPRECATED_PACKAGE_NAME_PROPERTY)
98     //                .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
99     //                .build())
100     //        .addProperty(new AppSearchSchema.BytesPropertyConfig.Builder(
101     //                DEPRECATED_SHA_256_CERT_PROPERTY)
102     //                .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
103     //                .build())
104     //        .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
105     //                DEPRECATED_ACCESSIBLE_SCHEMA_PROPERTY)
106     //                .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
107     //                .build())
108     //        .build();
109     // ---------------------------------------------------------------------------------------------
110 
111     /** Returns whether the given schema type is deprecated. */
isDeprecatedType(@onNull String schemaType)112     static boolean isDeprecatedType(@NonNull String schemaType) {
113         return schemaType.equals(DEPRECATED_VISIBILITY_SCHEMA_TYPE)
114                 || schemaType.equals(DEPRECATED_PACKAGE_SCHEMA_TYPE);
115     }
116 
117     /**
118      * Adds a prefix to create a deprecated visibility document's id.
119      *
120      * @param packageName Package to which the visibility doc refers.
121      * @param databaseName Database to which the visibility doc refers.
122      * @return deprecated visibility document's id.
123      */
124     @NonNull
getDeprecatedVisibilityDocumentId( @onNull String packageName, @NonNull String databaseName)125     static String getDeprecatedVisibilityDocumentId(
126             @NonNull String packageName, @NonNull String databaseName) {
127         return DEPRECATED_ID_PREFIX + PrefixUtil.createPrefix(packageName, databaseName);
128     }
129 
130     /** Reads all stored deprecated Visibility Document in version 0 from icing. */
getVisibilityDocumentsInVersion0( @onNull GetSchemaResponse getSchemaResponse, @NonNull AppSearchImpl appSearchImpl)131     static List<GenericDocument> getVisibilityDocumentsInVersion0(
132             @NonNull GetSchemaResponse getSchemaResponse, @NonNull AppSearchImpl appSearchImpl)
133             throws AppSearchException {
134         if (!hasDeprecatedType(getSchemaResponse)) {
135             return new ArrayList<>();
136         }
137         Map<String, Set<String>> packageToDatabases = appSearchImpl.getPackageToDatabases();
138         List<GenericDocument> deprecatedDocuments = new ArrayList<>(packageToDatabases.size());
139         for (Map.Entry<String, Set<String>> entry : packageToDatabases.entrySet()) {
140             String packageName = entry.getKey();
141             if (packageName.equals(VisibilityStore.VISIBILITY_PACKAGE_NAME)) {
142                 continue; // Our own package. Skip.
143             }
144             for (String databaseName : entry.getValue()) {
145                 try {
146                     // Note: We use the other clients' prefixed names as ids
147                     deprecatedDocuments.add(
148                             appSearchImpl.getDocument(
149                                     VisibilityStore.VISIBILITY_PACKAGE_NAME,
150                                     VisibilityStore.VISIBILITY_DATABASE_NAME,
151                                     VisibilityToDocumentConverter.VISIBILITY_DOCUMENT_NAMESPACE,
152                                     getDeprecatedVisibilityDocumentId(packageName, databaseName),
153                                     /* typePropertyPaths= */ Collections.emptyMap()));
154                 } catch (AppSearchException e) {
155                     if (e.getResultCode() == AppSearchResult.RESULT_NOT_FOUND) {
156                         // TODO(b/172068212): This indicates some desync error. We were expecting a
157                         //  document, but didn't find one. Should probably reset AppSearch instead
158                         //  of ignoring it.
159                         continue;
160                     }
161                     // Otherwise, this is some other error we should pass up.
162                     throw e;
163                 }
164             }
165         }
166         return deprecatedDocuments;
167     }
168 
169     /**
170      * Converts the given list of deprecated Visibility Documents into a Map of {@code
171      * <PrefixedSchemaType, VisibilityDocument.Builder of the latest version>}.
172      *
173      * @param visibilityDocumentV0s The deprecated Visibility Document we found.
174      */
175     @NonNull
toVisibilityDocumentV1( @onNull List<GenericDocument> visibilityDocumentV0s)176     static List<VisibilityDocumentV1> toVisibilityDocumentV1(
177             @NonNull List<GenericDocument> visibilityDocumentV0s) {
178         Map<String, VisibilityDocumentV1.Builder> documentBuilderMap = new ArrayMap<>();
179 
180         // Set all visibility information into documentBuilderMap
181         for (int i = 0; i < visibilityDocumentV0s.size(); i++) {
182             GenericDocument visibilityDocumentV0 = visibilityDocumentV0s.get(i);
183 
184             // Read not displayed by system property field.
185             String[] notDisplayedBySystemSchemas =
186                     visibilityDocumentV0.getPropertyStringArray(
187                             DEPRECATED_NOT_DISPLAYED_BY_SYSTEM_PROPERTY);
188             if (notDisplayedBySystemSchemas != null) {
189                 for (String notDisplayedBySystemSchema : notDisplayedBySystemSchemas) {
190                     // SetSchemaRequest.Builder.build() make sure all schemas that has visibility
191                     // setting must present in the requests.
192                     VisibilityDocumentV1.Builder visibilityBuilder =
193                             getOrCreateBuilder(documentBuilderMap, notDisplayedBySystemSchema);
194                     visibilityBuilder.setNotDisplayedBySystem(true);
195                 }
196             }
197 
198             // Read visible to packages field.
199             GenericDocument[] deprecatedPackageDocuments =
200                     visibilityDocumentV0.getPropertyDocumentArray(
201                             DEPRECATED_VISIBLE_TO_PACKAGES_PROPERTY);
202             if (deprecatedPackageDocuments != null) {
203                 for (GenericDocument deprecatedPackageDocument : deprecatedPackageDocuments) {
204                     String prefixedSchemaType =
205                             Objects.requireNonNull(
206                                     deprecatedPackageDocument.getPropertyString(
207                                             DEPRECATED_ACCESSIBLE_SCHEMA_PROPERTY));
208                     VisibilityDocumentV1.Builder visibilityBuilder =
209                             getOrCreateBuilder(documentBuilderMap, prefixedSchemaType);
210                     String packageName =
211                             Objects.requireNonNull(
212                                     deprecatedPackageDocument.getPropertyString(
213                                             DEPRECATED_PACKAGE_NAME_PROPERTY));
214                     byte[] sha256Cert =
215                             Objects.requireNonNull(
216                                     deprecatedPackageDocument.getPropertyBytes(
217                                             DEPRECATED_SHA_256_CERT_PROPERTY));
218                     visibilityBuilder.addVisibleToPackage(
219                             new PackageIdentifier(packageName, sha256Cert));
220                 }
221             }
222         }
223         List<VisibilityDocumentV1> visibilityDocumentsV1 =
224                 new ArrayList<>(documentBuilderMap.size());
225         for (Map.Entry<String, VisibilityDocumentV1.Builder> entry :
226                 documentBuilderMap.entrySet()) {
227             visibilityDocumentsV1.add(entry.getValue().build());
228         }
229         return visibilityDocumentsV1;
230     }
231 
232     /**
233      * Return whether the database maybe has the oldest version of deprecated schema.
234      *
235      * <p>Since the current version number is 0, it is possible that the database is just empty and
236      * it return 0 as the default version number. So we need to check if the deprecated document
237      * presents to trigger the migration.
238      */
hasDeprecatedType(@onNull GetSchemaResponse getSchemaResponse)239     private static boolean hasDeprecatedType(@NonNull GetSchemaResponse getSchemaResponse) {
240         for (AppSearchSchema schema : getSchemaResponse.getSchemas()) {
241             if (VisibilityStoreMigrationHelperFromV0.isDeprecatedType(schema.getSchemaType())) {
242                 // Found deprecated type, we need to migrate visibility Document. And it's
243                 // not possible for us to find the latest visibility schema.
244                 return true;
245             }
246         }
247         return false;
248     }
249 
250     @NonNull
getOrCreateBuilder( @onNull Map<String, VisibilityDocumentV1.Builder> documentBuilderMap, @NonNull String schemaType)251     private static VisibilityDocumentV1.Builder getOrCreateBuilder(
252             @NonNull Map<String, VisibilityDocumentV1.Builder> documentBuilderMap,
253             @NonNull String schemaType) {
254         VisibilityDocumentV1.Builder builder = documentBuilderMap.get(schemaType);
255         if (builder == null) {
256             builder = new VisibilityDocumentV1.Builder(/* id= */ schemaType);
257             documentBuilderMap.put(schemaType, builder);
258         }
259         return builder;
260     }
261 }
262