• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 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.converter;
18 
19 import android.app.appsearch.AppSearchBlobHandle;
20 import android.app.appsearch.AppSearchSchema;
21 import android.app.appsearch.EmbeddingVector;
22 import android.app.appsearch.GenericDocument;
23 import android.app.appsearch.exceptions.AppSearchException;
24 
25 import com.android.appsearch.flags.Flags;
26 import com.android.server.appsearch.external.localstorage.AppSearchConfig;
27 import com.android.server.appsearch.external.localstorage.SchemaCache;
28 
29 import com.google.android.icing.proto.DocumentProto;
30 import com.google.android.icing.proto.DocumentProtoOrBuilder;
31 import com.google.android.icing.proto.PropertyProto;
32 import com.google.android.icing.proto.SchemaTypeConfigProto;
33 import com.google.protobuf.ByteString;
34 
35 import org.jspecify.annotations.NonNull;
36 
37 import java.util.ArrayList;
38 import java.util.Collections;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Objects;
42 
43 /**
44  * Translates a {@link GenericDocument} into a {@link DocumentProto}.
45  *
46  * @hide
47  */
48 public final class GenericDocumentToProtoConverter {
49     private static final String[] EMPTY_STRING_ARRAY = new String[0];
50     private static final long[] EMPTY_LONG_ARRAY = new long[0];
51     private static final double[] EMPTY_DOUBLE_ARRAY = new double[0];
52     private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0];
53     private static final byte[][] EMPTY_BYTES_ARRAY = new byte[0][0];
54     private static final GenericDocument[] EMPTY_DOCUMENT_ARRAY = new GenericDocument[0];
55     private static final EmbeddingVector[] EMPTY_EMBEDDING_ARRAY = new EmbeddingVector[0];
56 
GenericDocumentToProtoConverter()57     private GenericDocumentToProtoConverter() {}
58 
59     /** Converts a {@link GenericDocument} into a {@link DocumentProto}. */
60     @SuppressWarnings("unchecked")
toDocumentProto(@onNull GenericDocument document)61     public static @NonNull DocumentProto toDocumentProto(@NonNull GenericDocument document) {
62         Objects.requireNonNull(document);
63         DocumentProto.Builder mProtoBuilder = DocumentProto.newBuilder();
64         mProtoBuilder
65                 .setUri(document.getId())
66                 .setSchema(document.getSchemaType())
67                 .setNamespace(document.getNamespace())
68                 .setScore(document.getScore())
69                 .setTtlMs(document.getTtlMillis())
70                 .setCreationTimestampMs(document.getCreationTimestampMillis());
71         ArrayList<String> keys = new ArrayList<>(document.getPropertyNames());
72         Collections.sort(keys);
73         for (int i = 0; i < keys.size(); i++) {
74             String name = keys.get(i);
75             PropertyProto.Builder propertyProto = PropertyProto.newBuilder().setName(name);
76             Object property = document.getProperty(name);
77             if (property instanceof String[]) {
78                 String[] stringValues = (String[]) property;
79                 for (int j = 0; j < stringValues.length; j++) {
80                     propertyProto.addStringValues(stringValues[j]);
81                 }
82             } else if (property instanceof long[]) {
83                 long[] longValues = (long[]) property;
84                 for (int j = 0; j < longValues.length; j++) {
85                     propertyProto.addInt64Values(longValues[j]);
86                 }
87             } else if (property instanceof double[]) {
88                 double[] doubleValues = (double[]) property;
89                 for (int j = 0; j < doubleValues.length; j++) {
90                     propertyProto.addDoubleValues(doubleValues[j]);
91                 }
92             } else if (property instanceof boolean[]) {
93                 boolean[] booleanValues = (boolean[]) property;
94                 for (int j = 0; j < booleanValues.length; j++) {
95                     propertyProto.addBooleanValues(booleanValues[j]);
96                 }
97             } else if (property instanceof byte[][]) {
98                 byte[][] bytesValues = (byte[][]) property;
99                 for (int j = 0; j < bytesValues.length; j++) {
100                     propertyProto.addBytesValues(ByteString.copyFrom(bytesValues[j]));
101                 }
102             } else if (property instanceof GenericDocument[]) {
103                 GenericDocument[] documentValues = (GenericDocument[]) property;
104                 for (int j = 0; j < documentValues.length; j++) {
105                     DocumentProto proto = toDocumentProto(documentValues[j]);
106                     propertyProto.addDocumentValues(proto);
107                 }
108             } else if (property instanceof EmbeddingVector[]) {
109                 EmbeddingVector[] embeddingValues = (EmbeddingVector[]) property;
110                 for (int j = 0; j < embeddingValues.length; j++) {
111                     propertyProto.addVectorValues(embeddingVectorToVectorProto(embeddingValues[j]));
112                 }
113             } else if (property instanceof AppSearchBlobHandle[]) {
114                 AppSearchBlobHandle[] blobHandleValues = (AppSearchBlobHandle[]) property;
115                 for (int j = 0; j < blobHandleValues.length; j++) {
116                     propertyProto.addBlobHandleValues(
117                             BlobHandleToProtoConverter.toBlobHandleProto(blobHandleValues[j]));
118                 }
119             } else if (property == null) {
120                 throw new IllegalStateException(
121                         String.format("Property \"%s\" doesn't have any value!", name));
122             } else {
123                 throw new IllegalStateException(
124                         String.format(
125                                 "Property \"%s\" has unsupported value type %s",
126                                 name, property.getClass().toString()));
127             }
128             mProtoBuilder.addProperties(propertyProto);
129         }
130         return mProtoBuilder.build();
131     }
132 
133     /**
134      * Converts a {@link DocumentProto} into a {@link GenericDocument}.
135      *
136      * <p>In the case that the {@link DocumentProto} object proto has no values set, the converter
137      * searches for the matching property name in the {@link SchemaTypeConfigProto} object for the
138      * document, and infers the correct default value to set for the empty property based on the
139      * data type of the property defined by the schema type.
140      *
141      * @param proto the document to convert to a {@link GenericDocument} instance. The document
142      *     proto should have its package + database prefix stripped from its fields.
143      * @param prefix the package + database prefix used searching the {@code schemaTypeMap}.
144      * @param schemaCache The SchemaCache instance held in AppSearch.
145      */
146     @SuppressWarnings("deprecation")
toGenericDocument( @onNull DocumentProtoOrBuilder proto, @NonNull String prefix, @NonNull SchemaCache schemaCache, @NonNull AppSearchConfig config)147     public static @NonNull GenericDocument toGenericDocument(
148             @NonNull DocumentProtoOrBuilder proto,
149             @NonNull String prefix,
150             @NonNull SchemaCache schemaCache,
151             @NonNull AppSearchConfig config)
152             throws AppSearchException {
153         Objects.requireNonNull(proto);
154         Objects.requireNonNull(prefix);
155         Objects.requireNonNull(schemaCache);
156         Objects.requireNonNull(config);
157         Map<String, SchemaTypeConfigProto> schemaTypeMap =
158                 schemaCache.getSchemaMapForPrefix(prefix);
159 
160         GenericDocument.Builder<?> documentBuilder =
161                 new GenericDocument.Builder<>(
162                                 proto.getNamespace(), proto.getUri(), proto.getSchema())
163                         .setScore(proto.getScore())
164                         .setTtlMillis(proto.getTtlMs())
165                         .setCreationTimestampMillis(proto.getCreationTimestampMs());
166         String prefixedSchemaType = prefix + proto.getSchema();
167         if (config.shouldRetrieveParentInfo() && !Flags.enableSearchResultParentTypes()) {
168             List<String> parentSchemaTypes =
169                     schemaCache.getTransitiveUnprefixedParentSchemaTypes(
170                             prefix, prefixedSchemaType);
171             if (!parentSchemaTypes.isEmpty()) {
172                 if (config.shouldStoreParentInfoAsSyntheticProperty()) {
173                     documentBuilder.setPropertyString(
174                             GenericDocument.PARENT_TYPES_SYNTHETIC_PROPERTY,
175                             parentSchemaTypes.toArray(new String[0]));
176                 } else {
177                     documentBuilder.setParentTypes(parentSchemaTypes);
178                 }
179             }
180         }
181 
182         for (int i = 0; i < proto.getPropertiesCount(); i++) {
183             PropertyProto property = proto.getProperties(i);
184             String name = property.getName();
185             if (property.getStringValuesCount() > 0) {
186                 String[] values = new String[property.getStringValuesCount()];
187                 for (int j = 0; j < values.length; j++) {
188                     values[j] = property.getStringValues(j);
189                 }
190                 documentBuilder.setPropertyString(name, values);
191             } else if (property.getInt64ValuesCount() > 0) {
192                 long[] values = new long[property.getInt64ValuesCount()];
193                 for (int j = 0; j < values.length; j++) {
194                     values[j] = property.getInt64Values(j);
195                 }
196                 documentBuilder.setPropertyLong(name, values);
197             } else if (property.getDoubleValuesCount() > 0) {
198                 double[] values = new double[property.getDoubleValuesCount()];
199                 for (int j = 0; j < values.length; j++) {
200                     values[j] = property.getDoubleValues(j);
201                 }
202                 documentBuilder.setPropertyDouble(name, values);
203             } else if (property.getBooleanValuesCount() > 0) {
204                 boolean[] values = new boolean[property.getBooleanValuesCount()];
205                 for (int j = 0; j < values.length; j++) {
206                     values[j] = property.getBooleanValues(j);
207                 }
208                 documentBuilder.setPropertyBoolean(name, values);
209             } else if (property.getBytesValuesCount() > 0) {
210                 byte[][] values = new byte[property.getBytesValuesCount()][];
211                 for (int j = 0; j < values.length; j++) {
212                     values[j] = property.getBytesValues(j).toByteArray();
213                 }
214                 documentBuilder.setPropertyBytes(name, values);
215             } else if (property.getDocumentValuesCount() > 0) {
216                 GenericDocument[] values = new GenericDocument[property.getDocumentValuesCount()];
217                 for (int j = 0; j < values.length; j++) {
218                     values[j] =
219                             toGenericDocument(
220                                     property.getDocumentValues(j), prefix, schemaCache, config);
221                 }
222                 documentBuilder.setPropertyDocument(name, values);
223             } else if (property.getVectorValuesCount() > 0) {
224                 EmbeddingVector[] values = new EmbeddingVector[property.getVectorValuesCount()];
225                 for (int j = 0; j < values.length; j++) {
226                     values[j] = vectorProtoToEmbeddingVector(property.getVectorValues(j));
227                 }
228                 documentBuilder.setPropertyEmbedding(name, values);
229             } else if (property.getBlobHandleValuesCount() > 0) {
230                 AppSearchBlobHandle[] values =
231                         new AppSearchBlobHandle[property.getBlobHandleValuesCount()];
232                 for (int j = 0; j < values.length; j++) {
233                     values[j] =
234                             BlobHandleToProtoConverter.toAppSearchBlobHandle(
235                                     property.getBlobHandleValues(j));
236                 }
237                 documentBuilder.setPropertyBlobHandle(name, values);
238             } else {
239                 // TODO(b/184966497): Optimize by caching PropertyConfigProto
240                 SchemaTypeConfigProto schema =
241                         Objects.requireNonNull(schemaTypeMap.get(prefixedSchemaType));
242                 setEmptyProperty(name, documentBuilder, schema);
243             }
244         }
245         return documentBuilder.build();
246     }
247 
248     /** Converts a {@link PropertyProto.VectorProto} into an {@link EmbeddingVector}. */
vectorProtoToEmbeddingVector( PropertyProto.@onNull VectorProto vectorProto)249     public static @NonNull EmbeddingVector vectorProtoToEmbeddingVector(
250             PropertyProto.@NonNull VectorProto vectorProto) {
251         Objects.requireNonNull(vectorProto);
252 
253         float[] values = new float[vectorProto.getValuesCount()];
254         for (int i = 0; i < vectorProto.getValuesCount(); i++) {
255             values[i] = vectorProto.getValues(i);
256         }
257         return new EmbeddingVector(values, vectorProto.getModelSignature());
258     }
259 
260     /** Converts an {@link EmbeddingVector} into a {@link PropertyProto.VectorProto}. */
embeddingVectorToVectorProto( @onNull EmbeddingVector embedding)261     public static PropertyProto.@NonNull VectorProto embeddingVectorToVectorProto(
262             @NonNull EmbeddingVector embedding) {
263         Objects.requireNonNull(embedding);
264 
265         PropertyProto.VectorProto.Builder builder = PropertyProto.VectorProto.newBuilder();
266         for (int i = 0; i < embedding.getValues().length; i++) {
267             builder.addValues(embedding.getValues()[i]);
268         }
269         builder.setModelSignature(embedding.getModelSignature());
270         return builder.build();
271     }
272 
setEmptyProperty( @onNull String propertyName, GenericDocument.@NonNull Builder<?> documentBuilder, @NonNull SchemaTypeConfigProto schema)273     private static void setEmptyProperty(
274             @NonNull String propertyName,
275             GenericDocument.@NonNull Builder<?> documentBuilder,
276             @NonNull SchemaTypeConfigProto schema) {
277         @AppSearchSchema.PropertyConfig.DataType int dataType = 0;
278         for (int i = 0; i < schema.getPropertiesCount(); ++i) {
279             if (propertyName.equals(schema.getProperties(i).getPropertyName())) {
280                 dataType = schema.getProperties(i).getDataType().getNumber();
281                 break;
282             }
283         }
284 
285         switch (dataType) {
286             case AppSearchSchema.PropertyConfig.DATA_TYPE_STRING:
287                 documentBuilder.setPropertyString(propertyName, EMPTY_STRING_ARRAY);
288                 break;
289             case AppSearchSchema.PropertyConfig.DATA_TYPE_LONG:
290                 documentBuilder.setPropertyLong(propertyName, EMPTY_LONG_ARRAY);
291                 break;
292             case AppSearchSchema.PropertyConfig.DATA_TYPE_DOUBLE:
293                 documentBuilder.setPropertyDouble(propertyName, EMPTY_DOUBLE_ARRAY);
294                 break;
295             case AppSearchSchema.PropertyConfig.DATA_TYPE_BOOLEAN:
296                 documentBuilder.setPropertyBoolean(propertyName, EMPTY_BOOLEAN_ARRAY);
297                 break;
298             case AppSearchSchema.PropertyConfig.DATA_TYPE_BYTES:
299                 documentBuilder.setPropertyBytes(propertyName, EMPTY_BYTES_ARRAY);
300                 break;
301             case AppSearchSchema.PropertyConfig.DATA_TYPE_DOCUMENT:
302                 documentBuilder.setPropertyDocument(propertyName, EMPTY_DOCUMENT_ARRAY);
303                 break;
304             case AppSearchSchema.PropertyConfig.DATA_TYPE_EMBEDDING:
305                 documentBuilder.setPropertyEmbedding(propertyName, EMPTY_EMBEDDING_ARRAY);
306                 break;
307             default:
308                 throw new IllegalStateException("Unknown type of value: " + propertyName);
309         }
310     }
311 }
312