1 /*
2  * Copyright (C) 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.contactsindexer;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import android.annotation.NonNull;
22 import android.app.appsearch.AppSearchBatchResult;
23 import android.app.appsearch.AppSearchSchema;
24 import android.app.appsearch.AppSearchSession;
25 import android.app.appsearch.BatchResultCallback;
26 import android.app.appsearch.GenericDocument;
27 import android.app.appsearch.GetByDocumentIdRequest;
28 import android.app.appsearch.SearchResult;
29 import android.app.appsearch.SearchResults;
30 import android.app.appsearch.SearchSpec;
31 import android.app.appsearch.exceptions.AppSearchException;
32 import android.util.ArraySet;
33 
34 import com.android.server.appsearch.contactsindexer.appsearchtypes.ContactPoint;
35 import com.android.server.appsearch.contactsindexer.appsearchtypes.Person;
36 
37 import java.util.Collection;
38 import java.util.List;
39 import java.util.Objects;
40 import java.util.Set;
41 import java.util.concurrent.CompletableFuture;
42 import java.util.concurrent.ExecutionException;
43 import java.util.concurrent.Executor;
44 
45 class TestUtils {
46     // Schema
47     static final AppSearchSchema CONTACT_POINT_SCHEMA_WITH_APP_IDS_OPTIONAL =
48             new AppSearchSchema.Builder(
49                     ContactPoint.SCHEMA_TYPE)
50                     .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
51                             ContactPoint.CONTACT_POINT_PROPERTY_LABEL)
52                             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
53                             .setIndexingType(
54                                     AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
55                             .setTokenizerType(
56                                     AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
57                             .build())
58                     // This is repeated in the official builtin:ContactPoint.
59                     // appIds
60                     .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
61                             ContactPoint.CONTACT_POINT_PROPERTY_APP_ID)
62                             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL)
63                             .build())
64                     // address
65                     .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
66                             ContactPoint.CONTACT_POINT_PROPERTY_ADDRESS)
67                             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
68                             .setIndexingType(
69                                     AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
70                             .setTokenizerType(
71                                     AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
72                             .build())
73                     // email
74                     .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
75                             ContactPoint.CONTACT_POINT_PROPERTY_EMAIL)
76                             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
77                             .setIndexingType(
78                                     AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
79                             .setTokenizerType(
80                                     AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
81                             .build())
82                     // telephone
83                     .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
84                             ContactPoint.CONTACT_POINT_PROPERTY_TELEPHONE)
85                             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
86                             .setIndexingType(
87                                     AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
88                             .setTokenizerType(
89                                     AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
90                             .build())
91                     .build();
92 
93     static final AppSearchSchema CONTACT_POINT_SCHEMA_WITH_LABEL_REPEATED =
94             new AppSearchSchema.Builder(
95                     ContactPoint.SCHEMA_TYPE)
96                     .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
97                             ContactPoint.CONTACT_POINT_PROPERTY_LABEL)
98                             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
99                             .setIndexingType(
100                                     AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
101                             .setTokenizerType(
102                                     AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
103                             .build())
104                     // appIds
105                     .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
106                             ContactPoint.CONTACT_POINT_PROPERTY_APP_ID)
107                             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
108                             .build())
109                     // address
110                     .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
111                             ContactPoint.CONTACT_POINT_PROPERTY_ADDRESS)
112                             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
113                             .setIndexingType(
114                                     AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
115                             .setTokenizerType(
116                                     AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
117                             .build())
118                     // email
119                     .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
120                             ContactPoint.CONTACT_POINT_PROPERTY_EMAIL)
121                             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
122                             .setIndexingType(
123                                     AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
124                             .setTokenizerType(
125                                     AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
126                             .build())
127                     // telephone
128                     .addProperty(new AppSearchSchema.StringPropertyConfig.Builder(
129                             ContactPoint.CONTACT_POINT_PROPERTY_TELEPHONE)
130                             .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_REPEATED)
131                             .setIndexingType(
132                                     AppSearchSchema.StringPropertyConfig.INDEXING_TYPE_PREFIXES)
133                             .setTokenizerType(
134                                     AppSearchSchema.StringPropertyConfig.TOKENIZER_TYPE_PLAIN)
135                             .build())
136                     .build();
137 
138     @NonNull
getDocsByIdAsync( @onNull AppSearchSession session, @NonNull Collection<String> ids, @NonNull Executor executor)139     public static CompletableFuture<AppSearchBatchResult> getDocsByIdAsync(
140             @NonNull AppSearchSession session, @NonNull Collection<String> ids,
141             @NonNull Executor executor) {
142         Objects.requireNonNull(session);
143         Objects.requireNonNull(ids);
144         Objects.requireNonNull(executor);
145 
146         CompletableFuture<AppSearchBatchResult> future = new CompletableFuture<>();
147         GetByDocumentIdRequest request = new GetByDocumentIdRequest.Builder(
148                 AppSearchHelper.NAMESPACE_NAME).addIds(ids).build();
149         session.getByDocumentId(request, executor,
150                 new BatchResultCallback<String, GenericDocument>() {
151                     @Override
152                     public void onResult(AppSearchBatchResult<String, GenericDocument> result) {
153                         future.complete(result);
154                     }
155 
156                     @Override
157                     public void onSystemError(Throwable throwable) {
158                         future.completeExceptionally(throwable);
159                     }
160                 });
161 
162         return future;
163     }
164 
165     @NonNull
getNextPageAsync( @onNull AppSearchSession session, @NonNull SearchResults searchResults, @NonNull Executor executor)166     public static CompletableFuture<List<SearchResult>> getNextPageAsync(
167             @NonNull AppSearchSession session,
168             @NonNull SearchResults searchResults,
169             @NonNull Executor executor) {
170         Objects.requireNonNull(searchResults);
171         Objects.requireNonNull(executor);
172 
173         CompletableFuture<List<SearchResult>> future = new CompletableFuture<>();
174         searchResults.getNextPage(executor, result -> {
175             if (result.isSuccess()) {
176                 future.complete(result.getResultValue());
177             } else {
178                 future.completeExceptionally(
179                         new AppSearchException(result.getResultCode(), result.getErrorMessage()));
180             }
181         });
182 
183         return future;
184     }
185 
186     @NonNull
getDocIdsByQuery(@onNull AppSearchSession session, @NonNull String query, @NonNull SearchSpec spec, @NonNull Executor executor)187     public static Set<String> getDocIdsByQuery(@NonNull AppSearchSession session,
188             @NonNull String query,
189             @NonNull SearchSpec spec,
190             @NonNull Executor executor) {
191         Objects.requireNonNull(session);
192         Objects.requireNonNull(query);
193         Objects.requireNonNull(spec);
194         Objects.requireNonNull(executor);
195 
196         SearchResults results = session.search(query, spec);
197         Set<String> allIds = new ArraySet<>();
198         try {
199             while (true) {
200                 List<SearchResult> docs = getNextPageAsync(session, results, executor).get();
201                 if (docs.isEmpty()) {
202                     break;
203                 }
204                 for (int i = 0; i < docs.size(); ++i) {
205                     allIds.add(docs.get(i).getGenericDocument().getId());
206                 }
207             }
208         } catch (InterruptedException | ExecutionException | IllegalStateException e) {
209             // do nothing.
210         }
211 
212         return allIds;
213     }
214 }
215