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 android.app.appsearch;
18 
19 import static android.app.appsearch.AppSearchResult.RESULT_INTERNAL_ERROR;
20 import static android.app.appsearch.SearchSessionUtil.safeExecute;
21 
22 import android.annotation.CallbackExecutor;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.app.appsearch.aidl.AppSearchAttributionSource;
26 import android.app.appsearch.aidl.AppSearchBatchResultParcel;
27 import android.app.appsearch.aidl.AppSearchResultParcel;
28 import android.app.appsearch.aidl.DocumentsParcel;
29 import android.app.appsearch.aidl.GetDocumentsAidlRequest;
30 import android.app.appsearch.aidl.GetNamespacesAidlRequest;
31 import android.app.appsearch.aidl.GetSchemaAidlRequest;
32 import android.app.appsearch.aidl.GetStorageInfoAidlRequest;
33 import android.app.appsearch.aidl.IAppSearchBatchResultCallback;
34 import android.app.appsearch.aidl.IAppSearchManager;
35 import android.app.appsearch.aidl.IAppSearchResultCallback;
36 import android.app.appsearch.aidl.InitializeAidlRequest;
37 import android.app.appsearch.aidl.PersistToDiskAidlRequest;
38 import android.app.appsearch.aidl.PutDocumentsAidlRequest;
39 import android.app.appsearch.aidl.RemoveByDocumentIdAidlRequest;
40 import android.app.appsearch.aidl.RemoveByQueryAidlRequest;
41 import android.app.appsearch.aidl.ReportUsageAidlRequest;
42 import android.app.appsearch.aidl.SearchSuggestionAidlRequest;
43 import android.app.appsearch.aidl.SetSchemaAidlRequest;
44 import android.app.appsearch.exceptions.AppSearchException;
45 import android.app.appsearch.safeparcel.GenericDocumentParcel;
46 import android.app.appsearch.stats.SchemaMigrationStats;
47 import android.app.appsearch.util.ExceptionUtil;
48 import android.app.appsearch.util.SchemaMigrationUtil;
49 import android.os.Build;
50 import android.os.RemoteException;
51 import android.os.SystemClock;
52 import android.os.UserHandle;
53 import android.util.ArraySet;
54 import android.util.Log;
55 
56 import com.android.internal.util.Preconditions;
57 
58 import java.io.Closeable;
59 import java.io.File;
60 import java.io.IOException;
61 import java.util.ArrayList;
62 import java.util.List;
63 import java.util.Map;
64 import java.util.Objects;
65 import java.util.Set;
66 import java.util.concurrent.CountDownLatch;
67 import java.util.concurrent.ExecutionException;
68 import java.util.concurrent.Executor;
69 import java.util.concurrent.atomic.AtomicReference;
70 import java.util.function.Consumer;
71 
72 /**
73  * Provides a connection to a single AppSearch database.
74  *
75  * <p>An {@link AppSearchSession} instance provides access to database operations such as setting a
76  * schema, adding documents, and searching.
77  *
78  * <p>This class is thread safe.
79  *
80  * @see GlobalSearchSession
81  */
82 public final class AppSearchSession implements Closeable {
83     private static final String TAG = "AppSearchSession";
84 
85     private final AppSearchAttributionSource mCallerAttributionSource;
86     private final String mDatabaseName;
87     private final UserHandle mUserHandle;
88     private final IAppSearchManager mService;
89     @Nullable private final File mCacheDirectory;
90 
91     private boolean mIsMutated = false;
92     private boolean mIsClosed = false;
93 
94     /**
95      * Creates a search session for the client, defined by the {@code userHandle} and {@code
96      * packageName}.
97      *
98      * @param searchContext The {@link AppSearchManager.SearchContext} contains all information to
99      *     create a new {@link AppSearchSession}.
100      * @param service The {@link IAppSearchManager} service from which to make api calls.
101      * @param userHandle The user for which the session should be created.
102      * @param callerAttributionSource The attribution source containing the caller's package name
103      *     and uid.
104      * @param cacheDirectory The directory to create temporary files needed for migration. If this
105      *     is null, the default temporary-file directory (/data/local/tmp) will be used.
106      * @param executor Executor on which to invoke the callback.
107      * @param callback The {@link AppSearchResult}&lt;{@link AppSearchSession}&gt; of performing
108      *     this operation. Or a {@link AppSearchResult} with failure reason code and error
109      *     information.
110      */
createSearchSession( @onNull AppSearchManager.SearchContext searchContext, @NonNull IAppSearchManager service, @NonNull UserHandle userHandle, @NonNull AppSearchAttributionSource callerAttributionSource, @Nullable File cacheDirectory, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<AppSearchResult<AppSearchSession>> callback)111     static void createSearchSession(
112             @NonNull AppSearchManager.SearchContext searchContext,
113             @NonNull IAppSearchManager service,
114             @NonNull UserHandle userHandle,
115             @NonNull AppSearchAttributionSource callerAttributionSource,
116             @Nullable File cacheDirectory,
117             @NonNull @CallbackExecutor Executor executor,
118             @NonNull Consumer<AppSearchResult<AppSearchSession>> callback) {
119         AppSearchSession searchSession =
120                 new AppSearchSession(
121                         service,
122                         userHandle,
123                         callerAttributionSource,
124                         searchContext.mDatabaseName,
125                         cacheDirectory);
126         searchSession.initialize(executor, callback);
127     }
128 
129     // NOTE: No instance of this class should be created or returned except via initialize().
130     // Once the callback.accept has been called here, the class is ready to use.
initialize( @onNull @allbackExecutor Executor executor, @NonNull Consumer<AppSearchResult<AppSearchSession>> callback)131     private void initialize(
132             @NonNull @CallbackExecutor Executor executor,
133             @NonNull Consumer<AppSearchResult<AppSearchSession>> callback) {
134         try {
135             mService.initialize(
136                     new InitializeAidlRequest(
137                             mCallerAttributionSource,
138                             mUserHandle,
139                             /* binderCallStartTimeMillis= */ SystemClock.elapsedRealtime()),
140                     new IAppSearchResultCallback.Stub() {
141                         @Override
142                         @SuppressWarnings({"rawtypes", "unchecked"})
143                         public void onResult(AppSearchResultParcel resultParcel) {
144                             safeExecute(
145                                     executor,
146                                     callback,
147                                     () -> {
148                                         AppSearchResult<Void> result = resultParcel.getResult();
149                                         if (result.isSuccess()) {
150                                             callback.accept(
151                                                     AppSearchResult.newSuccessfulResult(
152                                                             AppSearchSession.this));
153                                         } else {
154                                             callback.accept(
155                                                     AppSearchResult.newFailedResult(result));
156                                         }
157                                     });
158                         }
159                     });
160         } catch (RemoteException e) {
161             ExceptionUtil.handleRemoteException(e);
162         }
163     }
164 
AppSearchSession( @onNull IAppSearchManager service, @NonNull UserHandle userHandle, @NonNull AppSearchAttributionSource callerAttributionSource, @NonNull String databaseName, @Nullable File cacheDirectory)165     private AppSearchSession(
166             @NonNull IAppSearchManager service,
167             @NonNull UserHandle userHandle,
168             @NonNull AppSearchAttributionSource callerAttributionSource,
169             @NonNull String databaseName,
170             @Nullable File cacheDirectory) {
171         mService = service;
172         mUserHandle = userHandle;
173         mCallerAttributionSource = callerAttributionSource;
174         mDatabaseName = databaseName;
175         mCacheDirectory = cacheDirectory;
176     }
177 
178     /**
179      * Sets the schema that represents the organizational structure of data within the AppSearch
180      * database.
181      *
182      * <p>Upon creating an {@link AppSearchSession}, {@link #setSchema} should be called. If the
183      * schema needs to be updated, or it has not been previously set, then the provided schema will
184      * be saved and persisted to disk. Otherwise, {@link #setSchema} is handled efficiently as a
185      * no-op call.
186      *
187      * @param request the schema to set or update the AppSearch database to.
188      * @param workExecutor Executor on which to schedule heavy client-side background work such as
189      *     transforming documents.
190      * @param callbackExecutor Executor on which to invoke the callback.
191      * @param callback Callback to receive errors resulting from setting the schema. If the
192      *     operation succeeds, the callback will be invoked with {@code null}.
193      */
setSchema( @onNull SetSchemaRequest request, @NonNull Executor workExecutor, @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback)194     public void setSchema(
195             @NonNull SetSchemaRequest request,
196             @NonNull Executor workExecutor,
197             @NonNull @CallbackExecutor Executor callbackExecutor,
198             @NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) {
199         Objects.requireNonNull(request);
200         Objects.requireNonNull(workExecutor);
201         Objects.requireNonNull(callbackExecutor);
202         Objects.requireNonNull(callback);
203         Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
204         List<AppSearchSchema> schemaList = new ArrayList<>(request.getSchemas());
205         for (int i = 0; i < schemaList.size(); i++) {
206             if (!schemaList.get(i).getParentTypes().isEmpty()
207                     && Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
208                 throw new UnsupportedOperationException(
209                         "SCHEMA_ADD_PARENT_TYPE is not available on this AppSearch "
210                                 + "implementation.");
211             }
212         }
213 
214         // Extract a List<VisibilityConfig> from the request
215         List<InternalVisibilityConfig> visibilityConfigs =
216                 InternalVisibilityConfig.toInternalVisibilityConfigs(request);
217         // No need to trigger migration if user never set migrator
218         if (request.getMigrators().isEmpty()) {
219             setSchemaNoMigrations(
220                     request, schemaList, visibilityConfigs, callbackExecutor, callback);
221         } else {
222             setSchemaWithMigrations(
223                     request,
224                     schemaList,
225                     visibilityConfigs,
226                     workExecutor,
227                     callbackExecutor,
228                     callback);
229         }
230         mIsMutated = true;
231     }
232 
233     /**
234      * Retrieves the schema most recently successfully provided to {@link #setSchema}.
235      *
236      * @param executor Executor on which to invoke the callback.
237      * @param callback Callback to receive the pending results of schema.
238      */
getSchema( @onNull @allbackExecutor Executor executor, @NonNull Consumer<AppSearchResult<GetSchemaResponse>> callback)239     public void getSchema(
240             @NonNull @CallbackExecutor Executor executor,
241             @NonNull Consumer<AppSearchResult<GetSchemaResponse>> callback) {
242         Objects.requireNonNull(executor);
243         Objects.requireNonNull(callback);
244         String targetPackageName = mCallerAttributionSource.getPackageName();
245         Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
246         try {
247             mService.getSchema(
248                     new GetSchemaAidlRequest(
249                             mCallerAttributionSource,
250                             targetPackageName,
251                             mDatabaseName,
252                             mUserHandle,
253                             /* binderCallStartTimeMillis= */ SystemClock.elapsedRealtime(),
254                             /* isForEnterprise= */ false),
255                     new IAppSearchResultCallback.Stub() {
256                         @Override
257                         @SuppressWarnings({"rawtypes", "unchecked"})
258                         public void onResult(AppSearchResultParcel resultParcel) {
259                             safeExecute(
260                                     executor,
261                                     callback,
262                                     () -> {
263                                         AppSearchResult<GetSchemaResponse> result =
264                                                 resultParcel.getResult();
265                                         if (result.isSuccess()) {
266                                             GetSchemaResponse response =
267                                                     Objects.requireNonNull(result.getResultValue());
268                                             callback.accept(
269                                                     AppSearchResult.newSuccessfulResult(response));
270                                         } else {
271                                             callback.accept(
272                                                     AppSearchResult.newFailedResult(result));
273                                         }
274                                     });
275                         }
276                     });
277         } catch (RemoteException e) {
278             ExceptionUtil.handleRemoteException(e);
279         }
280     }
281 
282     /**
283      * Retrieves the set of all namespaces in the current database with at least one document.
284      *
285      * @param executor Executor on which to invoke the callback.
286      * @param callback Callback to receive the namespaces.
287      */
getNamespaces( @onNull @allbackExecutor Executor executor, @NonNull Consumer<AppSearchResult<Set<String>>> callback)288     public void getNamespaces(
289             @NonNull @CallbackExecutor Executor executor,
290             @NonNull Consumer<AppSearchResult<Set<String>>> callback) {
291         Objects.requireNonNull(executor);
292         Objects.requireNonNull(callback);
293         Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
294         try {
295             mService.getNamespaces(
296                     new GetNamespacesAidlRequest(
297                             mCallerAttributionSource,
298                             mDatabaseName,
299                             mUserHandle,
300                             /* binderCallStartTimeMillis= */ SystemClock.elapsedRealtime()),
301                     new IAppSearchResultCallback.Stub() {
302                         @Override
303                         @SuppressWarnings({"rawtypes", "unchecked"})
304                         public void onResult(AppSearchResultParcel resultParcel) {
305                             safeExecute(
306                                     executor,
307                                     callback,
308                                     () -> {
309                                         AppSearchResult<List<String>> result =
310                                                 resultParcel.getResult();
311                                         if (result.isSuccess()) {
312                                             Set<String> namespaces =
313                                                     new ArraySet<>(result.getResultValue());
314                                             callback.accept(
315                                                     AppSearchResult.newSuccessfulResult(
316                                                             namespaces));
317                                         } else {
318                                             callback.accept(
319                                                     AppSearchResult.newFailedResult(result));
320                                         }
321                                     });
322                         }
323                     });
324         } catch (RemoteException e) {
325             ExceptionUtil.handleRemoteException(e);
326         }
327     }
328 
329     /**
330      * Indexes documents into the {@link AppSearchSession} database.
331      *
332      * <p>Each {@link GenericDocument} object must have a {@code schemaType} field set to an {@link
333      * AppSearchSchema} type that has been previously registered by calling the {@link #setSchema}
334      * method.
335      *
336      * @param request containing documents to be indexed.
337      * @param executor Executor on which to invoke the callback.
338      * @param callback Callback to receive pending result of performing this operation. The keys of
339      *     the returned {@link AppSearchBatchResult} are the IDs of the input documents. The values
340      *     are {@code null} if they were successfully indexed, or a failed {@link AppSearchResult}
341      *     otherwise. If an unexpected internal error occurs in the AppSearch service, {@link
342      *     BatchResultCallback#onSystemError} will be invoked with a {@link Throwable}.
343      */
put( @onNull PutDocumentsRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull BatchResultCallback<String, Void> callback)344     public void put(
345             @NonNull PutDocumentsRequest request,
346             @NonNull @CallbackExecutor Executor executor,
347             @NonNull BatchResultCallback<String, Void> callback) {
348         Objects.requireNonNull(request);
349         Objects.requireNonNull(executor);
350         Objects.requireNonNull(callback);
351         Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
352         DocumentsParcel documentsParcel =
353                 new DocumentsParcel(
354                         toGenericDocumentParcels(request.getGenericDocuments()),
355                         toGenericDocumentParcels(request.getTakenActionGenericDocuments()));
356         try {
357             mService.putDocuments(
358                     new PutDocumentsAidlRequest(
359                             mCallerAttributionSource,
360                             mDatabaseName,
361                             documentsParcel,
362                             mUserHandle,
363                             /* binderCallStartTimeMillis= */ SystemClock.elapsedRealtime()),
364                     new IAppSearchBatchResultCallback.Stub() {
365                         @Override
366                         @SuppressWarnings({"rawtypes", "unchecked"})
367                         public void onResult(AppSearchBatchResultParcel resultParcel) {
368                             safeExecute(
369                                     executor,
370                                     callback,
371                                     () -> callback.onResult(resultParcel.getResult()));
372                         }
373 
374                         @Override
375                         public void onSystemError(AppSearchResultParcel resultParcel) {
376                             safeExecute(
377                                     executor,
378                                     callback,
379                                     () ->
380                                             SearchSessionUtil.sendSystemErrorToCallback(
381                                                     resultParcel.getResult(), callback));
382                         }
383                     });
384             mIsMutated = true;
385         } catch (RemoteException e) {
386             ExceptionUtil.handleRemoteException(e);
387         }
388     }
389 
390     /**
391      * Gets {@link GenericDocument} objects by document IDs in a namespace from the {@link
392      * AppSearchSession} database.
393      *
394      * @param request a request containing a namespace and IDs to get documents for.
395      * @param executor Executor on which to invoke the callback.
396      * @param callback Callback to receive the pending result of performing this operation. The keys
397      *     of the returned {@link AppSearchBatchResult} are the input IDs. The values are the
398      *     returned {@link GenericDocument}s on success, or a failed {@link AppSearchResult}
399      *     otherwise. IDs that are not found will return a failed {@link AppSearchResult} with a
400      *     result code of {@link AppSearchResult#RESULT_NOT_FOUND}. If an unexpected internal error
401      *     occurs in the AppSearch service, {@link BatchResultCallback#onSystemError} will be
402      *     invoked with a {@link Throwable}.
403      */
getByDocumentId( @onNull GetByDocumentIdRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull BatchResultCallback<String, GenericDocument> callback)404     public void getByDocumentId(
405             @NonNull GetByDocumentIdRequest request,
406             @NonNull @CallbackExecutor Executor executor,
407             @NonNull BatchResultCallback<String, GenericDocument> callback) {
408         Objects.requireNonNull(request);
409         Objects.requireNonNull(executor);
410         Objects.requireNonNull(callback);
411         String targetPackageName = mCallerAttributionSource.getPackageName();
412         Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
413         try {
414             mService.getDocuments(
415                     new GetDocumentsAidlRequest(
416                             mCallerAttributionSource,
417                             targetPackageName,
418                             mDatabaseName,
419                             request,
420                             mUserHandle,
421                             /* binderCallStartTimeMillis= */ SystemClock.elapsedRealtime(),
422                             /* isForEnterprise= */ false),
423                     SearchSessionUtil.createGetDocumentCallback(executor, callback));
424         } catch (RemoteException e) {
425             ExceptionUtil.handleRemoteException(e);
426         }
427     }
428 
429     /**
430      * Retrieves documents from the open {@link AppSearchSession} that match a given query string
431      * and type of search provided.
432      *
433      * <p>Query strings can be empty, contain one term with no operators, or contain multiple terms
434      * and operators.
435      *
436      * <p>For query strings that are empty, all documents that match the {@link SearchSpec} will be
437      * returned.
438      *
439      * <p>For query strings with a single term and no operators, documents that match the provided
440      * query string and {@link SearchSpec} will be returned.
441      *
442      * <p>The following operators are supported:
443      *
444      * <ul>
445      *   <li>AND (implicit)
446      *       <p>AND is an operator that matches documents that contain <i>all</i> provided terms.
447      *       <p><b>NOTE:</b> A space between terms is treated as an "AND" operator. Explicitly
448      *       including "AND" in a query string will treat "AND" as a term, returning documents that
449      *       also contain "AND".
450      *       <p>Example: "apple AND banana" matches documents that contain the terms "apple", "and",
451      *       "banana".
452      *       <p>Example: "apple banana" matches documents that contain both "apple" and "banana".
453      *       <p>Example: "apple banana cherry" matches documents that contain "apple", "banana", and
454      *       "cherry".
455      *   <li>OR
456      *       <p>OR is an operator that matches documents that contain <i>any</i> provided term.
457      *       <p>Example: "apple OR banana" matches documents that contain either "apple" or
458      *       "banana".
459      *       <p>Example: "apple OR banana OR cherry" matches documents that contain any of "apple",
460      *       "banana", or "cherry".
461      *   <li>Exclusion (-)
462      *       <p>Exclusion (-) is an operator that matches documents that <i>do not</i> contain the
463      *       provided term.
464      *       <p>Example: "-apple" matches documents that do not contain "apple".
465      *   <li>Grouped Terms
466      *       <p>For queries that require multiple operators and terms, terms can be grouped into
467      *       subqueries. Subqueries are contained within an open "(" and close ")" parenthesis.
468      *       <p>Example: "(donut OR bagel) (coffee OR tea)" matches documents that contain either
469      *       "donut" or "bagel" and either "coffee" or "tea".
470      *   <li>Property Restricts
471      *       <p>For queries that require a term to match a specific {@link AppSearchSchema} property
472      *       of a document, a ":" must be included between the property name and the term.
473      *       <p>Example: "subject:important" matches documents that contain the term "important" in
474      *       the "subject" property.
475      * </ul>
476      *
477      * <p>The above description covers the basic query operators. Additional advanced query operator
478      * features should be explicitly enabled in the SearchSpec and are described below.
479      *
480      * <p>LIST_FILTER_QUERY_LANGUAGE: This feature covers the expansion of the query language to
481      * conform to the definition of the list filters language (https://aip.dev/160). This includes:
482      *
483      * <ul>
484      *   <li>addition of explicit 'AND' and 'NOT' operators
485      *   <li>property restricts are allowed with groupings (ex. "prop:(a OR b)")
486      *   <li>addition of custom functions to control matching
487      * </ul>
488      *
489      * <p>The newly added custom functions covered by this feature are:
490      *
491      * <ul>
492      *   <li>createList(String...)
493      *   <li>search(String, List&lt;String&gt;)
494      *   <li>propertyDefined(String)
495      * </ul>
496      *
497      * <p>createList takes a variable number of strings and returns a list of strings. It is for use
498      * with search.
499      *
500      * <p>search takes a query string that will be parsed according to the supported query language
501      * and an optional list of strings that specify the properties to be restricted to. This exists
502      * as a convenience for multiple property restricts. So, for example, the query `(subject:foo OR
503      * body:foo) (subject:bar OR body:bar)` could be rewritten as `search("foo bar",
504      * createList("subject", "bar"))`.
505      *
506      * <p>propertyDefined takes a string specifying the property of interest and matches all
507      * documents of any type that defines the specified property (ex.
508      * `propertyDefined("sender.name")`). Note that propertyDefined will match so long as the
509      * document's type defines the specified property. It does NOT require that the document
510      * actually hold any values for this property.
511      *
512      * <p>NUMERIC_SEARCH: This feature covers numeric search expressions. In the query language, the
513      * values of properties that have {@link AppSearchSchema.LongPropertyConfig#INDEXING_TYPE_RANGE}
514      * set can be matched with a numeric search expression (the property, a supported comparator and
515      * an integer value). Supported comparators are <, <=, ==, >= and >.
516      *
517      * <p>Ex. `price < 10` will match all documents that has a numeric value in its price property
518      * that is less than 10.
519      *
520      * <p>VERBATIM_SEARCH: This feature covers the verbatim string operator (quotation marks).
521      *
522      * <p>Ex. `"foo/bar" OR baz` will ensure that 'foo/bar' is treated as a single 'verbatim' token.
523      *
524      * <p>Additional search specifications, such as filtering by {@link AppSearchSchema} type or
525      * adding projection, can be set by calling the corresponding {@link SearchSpec.Builder} setter.
526      *
527      * <p>This method is lightweight. The heavy work will be done in {@link
528      * SearchResults#getNextPage}.
529      *
530      * @param queryExpression query string to search.
531      * @param searchSpec spec for setting document filters, adding projection, setting term match
532      *     type, etc.
533      * @return a {@link SearchResults} object for retrieved matched documents.
534      */
535     @NonNull
search(@onNull String queryExpression, @NonNull SearchSpec searchSpec)536     public SearchResults search(@NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
537         Objects.requireNonNull(queryExpression);
538         Objects.requireNonNull(searchSpec);
539         Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
540         return new SearchResults(
541                 mService,
542                 mCallerAttributionSource,
543                 mDatabaseName,
544                 queryExpression,
545                 searchSpec,
546                 mUserHandle,
547                 /* isForEnterprise= */ false);
548     }
549 
550     /**
551      * Retrieves suggested Strings that could be used as {@code queryExpression} in {@link
552      * #search(String, SearchSpec)} API.
553      *
554      * <p>The {@code suggestionQueryExpression} can contain one term with no operators, or contain
555      * multiple terms and operators. Operators will be considered as a normal term. Please see the
556      * operator examples below. The {@code suggestionQueryExpression} must end with a valid term,
557      * the suggestions are generated based on the last term. If the input {@code
558      * suggestionQueryExpression} doesn't have a valid token, AppSearch will return an empty result
559      * list. Please see the invalid examples below.
560      *
561      * <p>Example: if there are following documents with content stored in AppSearch.
562      *
563      * <ul>
564      *   <li>document1: "term1"
565      *   <li>document2: "term1 term2"
566      *   <li>document3: "term1 term2 term3"
567      *   <li>document4: "org"
568      * </ul>
569      *
570      * <p>Search suggestions with the single term {@code suggestionQueryExpression} "t", the
571      * suggested results are:
572      *
573      * <ul>
574      *   <li>"term1" - Use it to be queryExpression in {@link #search} could get 3 {@link
575      *       SearchResult}s, which contains document 1, 2 and 3.
576      *   <li>"term2" - Use it to be queryExpression in {@link #search} could get 2 {@link
577      *       SearchResult}s, which contains document 2 and 3.
578      *   <li>"term3" - Use it to be queryExpression in {@link #search} could get 1 {@link
579      *       SearchResult}, which contains document 3.
580      * </ul>
581      *
582      * <p>Search suggestions with the multiple term {@code suggestionQueryExpression} "org t", the
583      * suggested result will be "org term1" - The last token is completed by the suggested String.
584      *
585      * <p>Operators in {@link #search} are supported.
586      *
587      * <p><b>NOTE:</b> Exclusion and Grouped Terms in the last term is not supported.
588      *
589      * <p>example: "apple -f": This Api will throw an {@link
590      * android.app.appsearch.exceptions.AppSearchException} with {@link
591      * AppSearchResult#RESULT_INVALID_ARGUMENT}.
592      *
593      * <p>example: "apple (f)": This Api will return an empty results.
594      *
595      * <p>Invalid example: All these input {@code suggestionQueryExpression} don't have a valid last
596      * token, AppSearch will return an empty result list.
597      *
598      * <ul>
599      *   <li>"" - Empty {@code suggestionQueryExpression}.
600      *   <li>"(f)" - Ending in a closed brackets.
601      *   <li>"f:" - Ending in an operator.
602      *   <li>"f " - Ending in trailing space.
603      * </ul>
604      *
605      * @param suggestionQueryExpression the non empty query string to search suggestions
606      * @param searchSuggestionSpec spec for setting document filters
607      * @param executor Executor on which to invoke the callback.
608      * @param callback Callback to receive the pending result of performing this operation, which is
609      *     a List of {@link SearchSuggestionResult} on success. The returned suggestion Strings are
610      *     ordered by the number of {@link SearchResult} you could get by using that suggestion in
611      *     {@link #search}.
612      * @see #search(String, SearchSpec)
613      */
searchSuggestion( @onNull String suggestionQueryExpression, @NonNull SearchSuggestionSpec searchSuggestionSpec, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<AppSearchResult<List<SearchSuggestionResult>>> callback)614     public void searchSuggestion(
615             @NonNull String suggestionQueryExpression,
616             @NonNull SearchSuggestionSpec searchSuggestionSpec,
617             @NonNull @CallbackExecutor Executor executor,
618             @NonNull Consumer<AppSearchResult<List<SearchSuggestionResult>>> callback) {
619         Objects.requireNonNull(suggestionQueryExpression);
620         Objects.requireNonNull(searchSuggestionSpec);
621         Objects.requireNonNull(executor);
622         Objects.requireNonNull(callback);
623         Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
624         try {
625             mService.searchSuggestion(
626                     new SearchSuggestionAidlRequest(
627                             mCallerAttributionSource,
628                             mDatabaseName,
629                             suggestionQueryExpression,
630                             searchSuggestionSpec,
631                             mUserHandle,
632                             /* binderCallStartTimeMillis= */ SystemClock.elapsedRealtime()),
633                     new IAppSearchResultCallback.Stub() {
634                         @Override
635                         @SuppressWarnings({"rawtypes", "unchecked"})
636                         public void onResult(AppSearchResultParcel resultParcel) {
637                             safeExecute(
638                                     executor,
639                                     callback,
640                                     () -> {
641                                         try {
642                                             AppSearchResult<List<SearchSuggestionResult>> result =
643                                                     resultParcel.getResult();
644                                             if (result.isSuccess()) {
645                                                 callback.accept(result);
646                                             } else {
647                                                 // TODO(b/261897334) save SDK errors/crashes and
648                                                 // send to
649                                                 //  server for logging.
650                                                 callback.accept(
651                                                         AppSearchResult.newFailedResult(result));
652                                             }
653                                         } catch (Exception e) {
654                                             callback.accept(
655                                                     AppSearchResult.throwableToFailedResult(e));
656                                         }
657                                     });
658                         }
659                     });
660         } catch (RemoteException e) {
661             ExceptionUtil.handleRemoteException(e);
662         }
663     }
664 
665     /**
666      * Reports usage of a particular document by namespace and ID.
667      *
668      * <p>A usage report represents an event in which a user interacted with or viewed a document.
669      *
670      * <p>For each call to {@link #reportUsage}, AppSearch updates usage count and usage recency
671      * metrics for that particular document. These metrics are used for ordering {@link #search}
672      * results by the {@link SearchSpec#RANKING_STRATEGY_USAGE_COUNT} and {@link
673      * SearchSpec#RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP} ranking strategies.
674      *
675      * <p>Reporting usage of a document is optional.
676      *
677      * @param request The usage reporting request.
678      * @param executor Executor on which to invoke the callback.
679      * @param callback Callback to receive errors. If the operation succeeds, the callback will be
680      *     invoked with {@code null}.
681      */
reportUsage( @onNull ReportUsageRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<AppSearchResult<Void>> callback)682     public void reportUsage(
683             @NonNull ReportUsageRequest request,
684             @NonNull @CallbackExecutor Executor executor,
685             @NonNull Consumer<AppSearchResult<Void>> callback) {
686         Objects.requireNonNull(request);
687         Objects.requireNonNull(executor);
688         Objects.requireNonNull(callback);
689         String targetPackageName = mCallerAttributionSource.getPackageName();
690         Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
691         try {
692             mService.reportUsage(
693                     new ReportUsageAidlRequest(
694                             mCallerAttributionSource,
695                             targetPackageName,
696                             mDatabaseName,
697                             request,
698                             /* systemUsage= */ false,
699                             mUserHandle,
700                             /* binderCallStartTimeMillis= */ SystemClock.elapsedRealtime()),
701                     new IAppSearchResultCallback.Stub() {
702                         @Override
703                         @SuppressWarnings({"rawtypes", "unchecked"})
704                         public void onResult(AppSearchResultParcel resultParcel) {
705                             safeExecute(
706                                     executor,
707                                     callback,
708                                     () -> callback.accept(resultParcel.getResult()));
709                         }
710                     });
711             mIsMutated = true;
712         } catch (RemoteException e) {
713             ExceptionUtil.handleRemoteException(e);
714         }
715     }
716 
717     /**
718      * Removes {@link GenericDocument} objects by document IDs in a namespace from the {@link
719      * AppSearchSession} database.
720      *
721      * <p>Removed documents will no longer be surfaced by {@link #search} or {@link
722      * #getByDocumentId} calls.
723      *
724      * <p>Once the database crosses the document count or byte usage threshold, removed documents
725      * will be deleted from disk.
726      *
727      * @param request {@link RemoveByDocumentIdRequest} with IDs in a namespace to remove from the
728      *     index.
729      * @param executor Executor on which to invoke the callback.
730      * @param callback Callback to receive the pending result of performing this operation. The keys
731      *     of the returned {@link AppSearchBatchResult} are the input document IDs. The values are
732      *     {@code null} on success, or a failed {@link AppSearchResult} otherwise. IDs that are not
733      *     found will return a failed {@link AppSearchResult} with a result code of {@link
734      *     AppSearchResult#RESULT_NOT_FOUND}. If an unexpected internal error occurs in the
735      *     AppSearch service, {@link BatchResultCallback#onSystemError} will be invoked with a
736      *     {@link Throwable}.
737      */
remove( @onNull RemoveByDocumentIdRequest request, @NonNull @CallbackExecutor Executor executor, @NonNull BatchResultCallback<String, Void> callback)738     public void remove(
739             @NonNull RemoveByDocumentIdRequest request,
740             @NonNull @CallbackExecutor Executor executor,
741             @NonNull BatchResultCallback<String, Void> callback) {
742         Objects.requireNonNull(request);
743         Objects.requireNonNull(executor);
744         Objects.requireNonNull(callback);
745         Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
746         try {
747             mService.removeByDocumentId(
748                     new RemoveByDocumentIdAidlRequest(
749                             mCallerAttributionSource,
750                             mDatabaseName,
751                             request,
752                             mUserHandle,
753                             /* binderCallStartTimeMillis= */ SystemClock.elapsedRealtime()),
754                     new IAppSearchBatchResultCallback.Stub() {
755                         @Override
756                         @SuppressWarnings({"rawtypes", "unchecked"})
757                         public void onResult(AppSearchBatchResultParcel resultParcel) {
758                             safeExecute(
759                                     executor,
760                                     callback,
761                                     () -> callback.onResult(resultParcel.getResult()));
762                         }
763 
764                         @Override
765                         @SuppressWarnings({"rawtypes", "unchecked"})
766                         public void onSystemError(AppSearchResultParcel resultParcel) {
767                             safeExecute(
768                                     executor,
769                                     callback,
770                                     () ->
771                                             SearchSessionUtil.sendSystemErrorToCallback(
772                                                     resultParcel.getResult(), callback));
773                         }
774                     });
775             mIsMutated = true;
776         } catch (RemoteException e) {
777             ExceptionUtil.handleRemoteException(e);
778         }
779     }
780 
781     /**
782      * Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they
783      * match the {@code queryExpression} in given namespaces and schemaTypes which is set via {@link
784      * SearchSpec.Builder#addFilterNamespaces} and {@link SearchSpec.Builder#addFilterSchemas}.
785      *
786      * <p>An empty {@code queryExpression} matches all documents.
787      *
788      * <p>An empty set of namespaces or schemaTypes matches all namespaces or schemaTypes in the
789      * current database.
790      *
791      * @param queryExpression Query String to search.
792      * @param searchSpec Spec containing schemaTypes, namespaces and query expression indicates how
793      *     document will be removed. All specific about how to scoring, ordering, snippeting and
794      *     resulting will be ignored.
795      * @param executor Executor on which to invoke the callback.
796      * @param callback Callback to receive errors resulting from removing the documents. If the
797      *     operation succeeds, the callback will be invoked with {@code null}.
798      */
remove( @onNull String queryExpression, @NonNull SearchSpec searchSpec, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<AppSearchResult<Void>> callback)799     public void remove(
800             @NonNull String queryExpression,
801             @NonNull SearchSpec searchSpec,
802             @NonNull @CallbackExecutor Executor executor,
803             @NonNull Consumer<AppSearchResult<Void>> callback) {
804         Objects.requireNonNull(queryExpression);
805         Objects.requireNonNull(searchSpec);
806         Objects.requireNonNull(executor);
807         Objects.requireNonNull(callback);
808         Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
809         if (searchSpec.getJoinSpec() != null) {
810             throw new IllegalArgumentException(
811                     "JoinSpec not allowed in removeByQuery, but " + "JoinSpec was provided.");
812         }
813         try {
814             mService.removeByQuery(
815                     new RemoveByQueryAidlRequest(
816                             mCallerAttributionSource,
817                             mDatabaseName,
818                             queryExpression,
819                             searchSpec,
820                             mUserHandle,
821                             /* binderCallStartTimeMillis= */ SystemClock.elapsedRealtime()),
822                     new IAppSearchResultCallback.Stub() {
823                         @Override
824                         @SuppressWarnings({"rawtypes", "unchecked"})
825                         public void onResult(AppSearchResultParcel resultParcel) {
826                             safeExecute(
827                                     executor,
828                                     callback,
829                                     () -> callback.accept(resultParcel.getResult()));
830                         }
831                     });
832             mIsMutated = true;
833         } catch (RemoteException e) {
834             ExceptionUtil.handleRemoteException(e);
835         }
836     }
837 
838     /**
839      * Gets the storage info for this {@link AppSearchSession} database.
840      *
841      * <p>This may take time proportional to the number of documents and may be inefficient to call
842      * repeatedly.
843      *
844      * @param executor Executor on which to invoke the callback.
845      * @param callback Callback to receive the storage info.
846      */
getStorageInfo( @onNull @allbackExecutor Executor executor, @NonNull Consumer<AppSearchResult<StorageInfo>> callback)847     public void getStorageInfo(
848             @NonNull @CallbackExecutor Executor executor,
849             @NonNull Consumer<AppSearchResult<StorageInfo>> callback) {
850         Objects.requireNonNull(executor);
851         Objects.requireNonNull(callback);
852         Preconditions.checkState(!mIsClosed, "AppSearchSession has already been closed");
853         try {
854             mService.getStorageInfo(
855                     new GetStorageInfoAidlRequest(
856                             mCallerAttributionSource,
857                             mDatabaseName,
858                             mUserHandle,
859                             /* binderCallStartTimeMillis= */ SystemClock.elapsedRealtime()),
860                     new IAppSearchResultCallback.Stub() {
861                         @Override
862                         @SuppressWarnings({"rawtypes", "unchecked"})
863                         public void onResult(AppSearchResultParcel resultParcel) {
864                             safeExecute(
865                                     executor,
866                                     callback,
867                                     () -> {
868                                         AppSearchResult<StorageInfo> result =
869                                                 resultParcel.getResult();
870                                         if (result.isSuccess()) {
871                                             callback.accept(
872                                                     AppSearchResult.newSuccessfulResult(
873                                                             result.getResultValue()));
874                                         } else {
875                                             callback.accept(
876                                                     AppSearchResult.newFailedResult(result));
877                                         }
878                                     });
879                         }
880                     });
881         } catch (RemoteException e) {
882             ExceptionUtil.handleRemoteException(e);
883         }
884     }
885 
886     /**
887      * Closes the {@link AppSearchSession} to persist all schema and document updates, additions,
888      * and deletes to disk.
889      */
890     @Override
close()891     public void close() {
892         if (mIsMutated && !mIsClosed) {
893             try {
894                 mService.persistToDisk(
895                         new PersistToDiskAidlRequest(
896                                 mCallerAttributionSource,
897                                 mUserHandle,
898                                 /* binderCallStartTimeMillis= */ SystemClock.elapsedRealtime()));
899                 mIsClosed = true;
900             } catch (RemoteException e) {
901                 Log.e(TAG, "Unable to close the AppSearchSession", e);
902             }
903         }
904     }
905 
906     /**
907      * Set schema to Icing for no-migration scenario.
908      *
909      * <p>We only need one time {@link #setSchema} call for no-migration scenario by using the
910      * forceoverride in the request.
911      */
setSchemaNoMigrations( @onNull SetSchemaRequest request, @NonNull List<AppSearchSchema> schemas, @NonNull List<InternalVisibilityConfig> visibilityConfigs, @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback)912     private void setSchemaNoMigrations(
913             @NonNull SetSchemaRequest request,
914             @NonNull List<AppSearchSchema> schemas,
915             @NonNull List<InternalVisibilityConfig> visibilityConfigs,
916             @NonNull @CallbackExecutor Executor executor,
917             @NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) {
918         try {
919             SetSchemaAidlRequest setSchemaAidlRequest =
920                     new SetSchemaAidlRequest(
921                             mCallerAttributionSource,
922                             mDatabaseName,
923                             schemas,
924                             visibilityConfigs,
925                             request.isForceOverride(),
926                             request.getVersion(),
927                             mUserHandle,
928                             /* binderCallStartTimeMillis= */ SystemClock.elapsedRealtime(),
929                             SchemaMigrationStats.NO_MIGRATION);
930             mService.setSchema(
931                     setSchemaAidlRequest,
932                     new IAppSearchResultCallback.Stub() {
933                         @Override
934                         @SuppressWarnings({"rawtypes", "unchecked"})
935                         public void onResult(AppSearchResultParcel resultParcel) {
936                             safeExecute(
937                                     executor,
938                                     callback,
939                                     () -> {
940                                         AppSearchResult<InternalSetSchemaResponse> result =
941                                                 resultParcel.getResult();
942                                         if (result.isSuccess()) {
943                                             try {
944                                                 InternalSetSchemaResponse
945                                                         internalSetSchemaResponse =
946                                                                 result.getResultValue();
947                                                 if (internalSetSchemaResponse == null) {
948                                                     // Ideally internalSetSchemaResponse should
949                                                     // always be non-null as result is success. In
950                                                     // other cases we directly put result in
951                                                     // AppSearchResult.newSuccessfulResult which
952                                                     // accepts a Nullable value, here we need to
953                                                     // get response by
954                                                     // internalSetSchemaResponse
955                                                     // .getSetSchemaResponse().
956                                                     callback.accept(
957                                                             AppSearchResult.newFailedResult(
958                                                                     RESULT_INTERNAL_ERROR,
959                                                                     "Received null"
960                                                                             + " InternalSetSchema"
961                                                                             + "Response"
962                                                                             + " during setSchema"
963                                                                             + " call"));
964                                                     return;
965                                                 }
966                                                 if (!internalSetSchemaResponse.isSuccess()) {
967                                                     // check is the set schema call failed
968                                                     // because incompatible changes. That's the only
969                                                     // case we swallowed in the
970                                                     // AppSearchImpl#setSchema().
971                                                     callback.accept(
972                                                             AppSearchResult.newFailedResult(
973                                                                     AppSearchResult
974                                                                             .RESULT_INVALID_SCHEMA,
975                                                                     internalSetSchemaResponse
976                                                                             .getErrorMessage()));
977                                                     return;
978                                                 }
979                                                 callback.accept(
980                                                         AppSearchResult.newSuccessfulResult(
981                                                                 internalSetSchemaResponse
982                                                                         .getSetSchemaResponse()));
983                                             } catch (RuntimeException e) {
984                                                 // TODO(b/261897334) save SDK errors/crashes and
985                                                 // send to
986                                                 //  server for logging.
987                                                 callback.accept(
988                                                         AppSearchResult.throwableToFailedResult(e));
989                                             }
990                                         } else {
991                                             callback.accept(
992                                                     AppSearchResult.newFailedResult(result));
993                                         }
994                                     });
995                         }
996                     });
997         } catch (RemoteException e) {
998             ExceptionUtil.handleRemoteException(e);
999         }
1000     }
1001 
1002     /**
1003      * Set schema to Icing for migration scenario.
1004      *
1005      * <p>First time {@link #setSchema} call with forceOverride is false gives us all incompatible
1006      * changes. After trigger migrations, the second time call {@link #setSchema} will actually
1007      * apply the changes.
1008      */
setSchemaWithMigrations( @onNull SetSchemaRequest request, @NonNull List<AppSearchSchema> schemas, @NonNull List<InternalVisibilityConfig> visibilityConfigs, @NonNull Executor workExecutor, @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback)1009     private void setSchemaWithMigrations(
1010             @NonNull SetSchemaRequest request,
1011             @NonNull List<AppSearchSchema> schemas,
1012             @NonNull List<InternalVisibilityConfig> visibilityConfigs,
1013             @NonNull Executor workExecutor,
1014             @NonNull @CallbackExecutor Executor callbackExecutor,
1015             @NonNull Consumer<AppSearchResult<SetSchemaResponse>> callback) {
1016         long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
1017         long waitExecutorStartLatencyMillis = SystemClock.elapsedRealtime();
1018         safeExecute(
1019                 workExecutor,
1020                 callback,
1021                 () -> {
1022                     try {
1023                         long waitExecutorEndLatencyMillis = SystemClock.elapsedRealtime();
1024                         String packageName = mCallerAttributionSource.getPackageName();
1025                         SchemaMigrationStats.Builder statsBuilder =
1026                                 new SchemaMigrationStats.Builder(packageName, mDatabaseName);
1027 
1028                         // Migration process
1029                         // 1. Validate and retrieve all active migrators.
1030                         long getSchemaLatencyStartTimeMillis = SystemClock.elapsedRealtime();
1031                         CountDownLatch getSchemaLatch = new CountDownLatch(1);
1032                         AtomicReference<AppSearchResult<GetSchemaResponse>> getSchemaResultRef =
1033                                 new AtomicReference<>();
1034                         getSchema(
1035                                 callbackExecutor,
1036                                 (result) -> {
1037                                     getSchemaResultRef.set(result);
1038                                     getSchemaLatch.countDown();
1039                                 });
1040                         getSchemaLatch.await();
1041                         AppSearchResult<GetSchemaResponse> getSchemaResult =
1042                                 getSchemaResultRef.get();
1043                         if (!getSchemaResult.isSuccess()) {
1044                             // TODO(b/261897334) save SDK errors/crashes and send to server for
1045                             // logging.
1046                             safeExecute(
1047                                     callbackExecutor,
1048                                     callback,
1049                                     () ->
1050                                             callback.accept(
1051                                                     AppSearchResult.newFailedResult(
1052                                                             getSchemaResult)));
1053                             return;
1054                         }
1055                         GetSchemaResponse getSchemaResponse =
1056                                 Objects.requireNonNull(getSchemaResult.getResultValue());
1057                         int currentVersion = getSchemaResponse.getVersion();
1058                         int finalVersion = request.getVersion();
1059                         Map<String, Migrator> activeMigrators =
1060                                 SchemaMigrationUtil.getActiveMigrators(
1061                                         getSchemaResponse.getSchemas(),
1062                                         request.getMigrators(),
1063                                         currentVersion,
1064                                         finalVersion);
1065                         long getSchemaLatencyEndTimeMillis = SystemClock.elapsedRealtime();
1066 
1067                         // No need to trigger migration if no migrator is active.
1068                         if (activeMigrators.isEmpty()) {
1069                             setSchemaNoMigrations(
1070                                     request,
1071                                     schemas,
1072                                     visibilityConfigs,
1073                                     callbackExecutor,
1074                                     callback);
1075                             return;
1076                         }
1077 
1078                         // 2. SetSchema with forceOverride=false, to retrieve the list of
1079                         // incompatible/deleted types.
1080                         long firstSetSchemaLatencyStartMillis = SystemClock.elapsedRealtime();
1081                         CountDownLatch setSchemaLatch = new CountDownLatch(1);
1082                         AtomicReference<AppSearchResult<InternalSetSchemaResponse>>
1083                                 setSchemaResultRef = new AtomicReference<>();
1084 
1085                         SetSchemaAidlRequest setSchemaAidlRequest =
1086                                 new SetSchemaAidlRequest(
1087                                         mCallerAttributionSource,
1088                                         mDatabaseName,
1089                                         schemas,
1090                                         visibilityConfigs,
1091                                         /* forceOverride= */ false,
1092                                         request.getVersion(),
1093                                         mUserHandle,
1094                                         /* binderCallStartTimeMillis= */ SystemClock
1095                                                 .elapsedRealtime(),
1096                                         SchemaMigrationStats.FIRST_CALL_GET_INCOMPATIBLE);
1097                         mService.setSchema(
1098                                 setSchemaAidlRequest,
1099                                 new IAppSearchResultCallback.Stub() {
1100                                     @Override
1101                                     @SuppressWarnings({"rawtypes", "unchecked"})
1102                                     public void onResult(AppSearchResultParcel resultParcel) {
1103                                         setSchemaResultRef.set(resultParcel.getResult());
1104                                         setSchemaLatch.countDown();
1105                                     }
1106                                 });
1107                         setSchemaLatch.await();
1108                         AppSearchResult<InternalSetSchemaResponse> setSchemaResult =
1109                                 setSchemaResultRef.get();
1110                         if (!setSchemaResult.isSuccess()) {
1111                             // TODO(b/261897334) save SDK errors/crashes and send to server for
1112                             // logging.
1113                             safeExecute(
1114                                     callbackExecutor,
1115                                     callback,
1116                                     () ->
1117                                             callback.accept(
1118                                                     AppSearchResult.newFailedResult(
1119                                                             setSchemaResult)));
1120                             return;
1121                         }
1122                         InternalSetSchemaResponse internalSetSchemaResponse1 =
1123                                 setSchemaResult.getResultValue();
1124                         if (internalSetSchemaResponse1 == null) {
1125                             safeExecute(
1126                                     callbackExecutor,
1127                                     callback,
1128                                     () ->
1129                                             callback.accept(
1130                                                     AppSearchResult.newFailedResult(
1131                                                             RESULT_INTERNAL_ERROR,
1132                                                             "Received null"
1133                                                                     + " InternalSetSchemaResponse"
1134                                                                     + " during setSchema call")));
1135                             return;
1136                         }
1137                         long firstSetSchemaLatencyEndTimeMillis = SystemClock.elapsedRealtime();
1138 
1139                         // 3. If forceOverride is false, check that all incompatible types will be
1140                         // migrated.
1141                         // If some aren't we must throw an error, rather than proceeding and
1142                         // deleting those
1143                         // types.
1144                         SchemaMigrationUtil.checkDeletedAndIncompatibleAfterMigration(
1145                                 internalSetSchemaResponse1, activeMigrators.keySet());
1146 
1147                         try (AppSearchMigrationHelper migrationHelper =
1148                                 new AppSearchMigrationHelper(
1149                                         mService,
1150                                         mUserHandle,
1151                                         mCallerAttributionSource,
1152                                         mDatabaseName,
1153                                         request.getSchemas(),
1154                                         mCacheDirectory)) {
1155 
1156                             // 4. Trigger migration for all migrators.
1157                             long queryAndTransformLatencyStartMillis =
1158                                     SystemClock.elapsedRealtime();
1159                             for (Map.Entry<String, Migrator> entry : activeMigrators.entrySet()) {
1160                                 migrationHelper.queryAndTransform(
1161                                         /* schemaType= */ entry.getKey(),
1162                                         /* migrator= */ entry.getValue(),
1163                                         currentVersion,
1164                                         finalVersion,
1165                                         statsBuilder);
1166                             }
1167                             long queryAndTransformLatencyEndTimeMillis =
1168                                     SystemClock.elapsedRealtime();
1169 
1170                             // 5. SetSchema a second time with forceOverride=true if the first
1171                             // attempted
1172                             // failed.
1173                             long secondSetSchemaLatencyStartMillis = SystemClock.elapsedRealtime();
1174                             InternalSetSchemaResponse internalSetSchemaResponse;
1175                             if (internalSetSchemaResponse1.isSuccess()) {
1176                                 internalSetSchemaResponse = internalSetSchemaResponse1;
1177                             } else {
1178                                 CountDownLatch setSchema2Latch = new CountDownLatch(1);
1179                                 AtomicReference<AppSearchResult<InternalSetSchemaResponse>>
1180                                         setSchema2ResultRef = new AtomicReference<>();
1181                                 // only trigger second setSchema() call if the first one is fail.
1182                                 SetSchemaAidlRequest setSchemaAidlRequest1 =
1183                                         new SetSchemaAidlRequest(
1184                                                 mCallerAttributionSource,
1185                                                 mDatabaseName,
1186                                                 schemas,
1187                                                 visibilityConfigs,
1188                                                 /* forceOverride= */ true,
1189                                                 request.getVersion(),
1190                                                 mUserHandle,
1191                                                 /* binderCallStartTimeMillis= */ SystemClock
1192                                                         .elapsedRealtime(),
1193                                                 SchemaMigrationStats.SECOND_CALL_APPLY_NEW_SCHEMA);
1194                                 mService.setSchema(
1195                                         setSchemaAidlRequest1,
1196                                         new IAppSearchResultCallback.Stub() {
1197                                             @Override
1198                                             @SuppressWarnings({"rawtypes", "unchecked"})
1199                                             public void onResult(
1200                                                     AppSearchResultParcel resultParcel) {
1201                                                 setSchema2ResultRef.set(resultParcel.getResult());
1202                                                 setSchema2Latch.countDown();
1203                                             }
1204                                         });
1205                                 setSchema2Latch.await();
1206                                 AppSearchResult<InternalSetSchemaResponse> setSchema2Result =
1207                                         setSchema2ResultRef.get();
1208                                 if (!setSchema2Result.isSuccess()) {
1209                                     // we failed to set the schema in second time with forceOverride
1210                                     // = true, which is an impossible case. Since we only swallow
1211                                     // the incompatible error in the first setSchema call, all other
1212                                     // errors will be thrown at the first time.
1213                                     // TODO(b/261897334) save SDK errors/crashes and send to server
1214                                     //  for logging.
1215                                     safeExecute(
1216                                             callbackExecutor,
1217                                             callback,
1218                                             () ->
1219                                                     callback.accept(
1220                                                             AppSearchResult.newFailedResult(
1221                                                                     setSchema2Result)));
1222                                     return;
1223                                 }
1224                                 InternalSetSchemaResponse internalSetSchemaResponse2 =
1225                                         setSchema2Result.getResultValue();
1226                                 if (internalSetSchemaResponse2 == null) {
1227                                     safeExecute(
1228                                             callbackExecutor,
1229                                             callback,
1230                                             () ->
1231                                                     callback.accept(
1232                                                             AppSearchResult.newFailedResult(
1233                                                                     RESULT_INTERNAL_ERROR,
1234                                                                     "Received null response"
1235                                                                             + " during setSchema"
1236                                                                             + " call")));
1237                                     return;
1238                                 }
1239                                 if (!internalSetSchemaResponse2.isSuccess()) {
1240                                     // Impossible case, we just set forceOverride to be true, we
1241                                     // should never fail in incompatible changes. And all other
1242                                     // cases should failed during the first call.
1243                                     // TODO(b/261897334) save SDK errors/crashes and send to server
1244                                     //  for logging.
1245                                     safeExecute(
1246                                             callbackExecutor,
1247                                             callback,
1248                                             () ->
1249                                                     callback.accept(
1250                                                             AppSearchResult.newFailedResult(
1251                                                                     RESULT_INTERNAL_ERROR,
1252                                                                     internalSetSchemaResponse2
1253                                                                             .getErrorMessage())));
1254                                     return;
1255                                 }
1256                                 internalSetSchemaResponse = internalSetSchemaResponse2;
1257                             }
1258                             long secondSetSchemaLatencyEndTimeMillis =
1259                                     SystemClock.elapsedRealtime();
1260 
1261                             statsBuilder
1262                                     .setExecutorAcquisitionLatencyMillis(
1263                                             (int)
1264                                                     (waitExecutorEndLatencyMillis
1265                                                             - waitExecutorStartLatencyMillis))
1266                                     .setGetSchemaLatencyMillis(
1267                                             (int)
1268                                                     (getSchemaLatencyEndTimeMillis
1269                                                             - getSchemaLatencyStartTimeMillis))
1270                                     .setFirstSetSchemaLatencyMillis(
1271                                             (int)
1272                                                     (firstSetSchemaLatencyEndTimeMillis
1273                                                             - firstSetSchemaLatencyStartMillis))
1274                                     .setIsFirstSetSchemaSuccess(
1275                                             internalSetSchemaResponse1.isSuccess())
1276                                     .setQueryAndTransformLatencyMillis(
1277                                             (int)
1278                                                     (queryAndTransformLatencyEndTimeMillis
1279                                                             - queryAndTransformLatencyStartMillis))
1280                                     .setSecondSetSchemaLatencyMillis(
1281                                             (int)
1282                                                     (secondSetSchemaLatencyEndTimeMillis
1283                                                             - secondSetSchemaLatencyStartMillis));
1284                             SetSchemaResponse.Builder responseBuilder =
1285                                     new SetSchemaResponse.Builder(
1286                                                     internalSetSchemaResponse
1287                                                             .getSetSchemaResponse())
1288                                             .addMigratedTypes(activeMigrators.keySet());
1289 
1290                             // 6. Put all the migrated documents into the index, now that the new
1291                             // schema is
1292                             // set.
1293                             AppSearchResult<SetSchemaResponse> putResult =
1294                                     migrationHelper.putMigratedDocuments(
1295                                             responseBuilder,
1296                                             statsBuilder,
1297                                             totalLatencyStartTimeMillis);
1298                             safeExecute(
1299                                     callbackExecutor, callback, () -> callback.accept(putResult));
1300                         }
1301                     } catch (RemoteException
1302                             | AppSearchException
1303                             | InterruptedException
1304                             | IOException
1305                             | ExecutionException
1306                             | RuntimeException e) {
1307                         // TODO(b/261897334) save SDK errors/crashes and send to server for logging.
1308                         safeExecute(
1309                                 callbackExecutor,
1310                                 callback,
1311                                 () -> callback.accept(AppSearchResult.throwableToFailedResult(e)));
1312                     }
1313                 });
1314     }
1315 
1316     @NonNull
toGenericDocumentParcels( List<GenericDocument> docs)1317     private static List<GenericDocumentParcel> toGenericDocumentParcels(
1318             List<GenericDocument> docs) {
1319         List<GenericDocumentParcel> docParcels = new ArrayList<>(docs.size());
1320         for (int i = 0; i < docs.size(); ++i) {
1321             docParcels.add(docs.get(i).getDocumentParcel());
1322         }
1323         return docParcels;
1324     }
1325 }
1326