1 /*
2  * Copyright (C) 2023 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.testutil;
18 
19 import android.annotation.NonNull;
20 import android.app.appsearch.AppSearchBatchResult;
21 import android.app.appsearch.AppSearchManager;
22 import android.app.appsearch.AppSearchResult;
23 import android.app.appsearch.EnterpriseGlobalSearchSession;
24 import android.app.appsearch.EnterpriseGlobalSearchSessionShim;
25 import android.app.appsearch.Features;
26 import android.app.appsearch.GenericDocument;
27 import android.app.appsearch.GetByDocumentIdRequest;
28 import android.app.appsearch.GetSchemaResponse;
29 import android.app.appsearch.SearchResults;
30 import android.app.appsearch.SearchResultsShim;
31 import android.app.appsearch.SearchSpec;
32 import android.app.appsearch.exceptions.AppSearchException;
33 import android.content.Context;
34 
35 import androidx.test.core.app.ApplicationProvider;
36 
37 import com.google.common.util.concurrent.Futures;
38 import com.google.common.util.concurrent.ListenableFuture;
39 import com.google.common.util.concurrent.SettableFuture;
40 
41 import java.util.Objects;
42 import java.util.concurrent.ExecutorService;
43 import java.util.concurrent.Executors;
44 
45 /**
46  * This test class adapts the AppSearch Framework API to ListenableFuture, so it can be tested via a
47  * consistent interface.
48  *
49  * @hide
50  */
51 public class EnterpriseGlobalSearchSessionShimImpl implements EnterpriseGlobalSearchSessionShim {
52     private final EnterpriseGlobalSearchSession mEnterpriseGlobalSearchSession;
53     private final ExecutorService mExecutor;
54 
55     /** Create an EnterpriseGlobalSearchSession with the application context. */
56     @NonNull
57     public static ListenableFuture<EnterpriseGlobalSearchSessionShim>
createEnterpriseGlobalSearchSessionAsync()58             createEnterpriseGlobalSearchSessionAsync() {
59         return createEnterpriseGlobalSearchSessionAsync(
60                 ApplicationProvider.getApplicationContext());
61     }
62 
63     /** Only for use when called from a non-instrumented context. */
64     @NonNull
65     public static ListenableFuture<EnterpriseGlobalSearchSessionShim>
createEnterpriseGlobalSearchSessionAsync(@onNull Context context)66             createEnterpriseGlobalSearchSessionAsync(@NonNull Context context) {
67         AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class);
68         SettableFuture<AppSearchResult<EnterpriseGlobalSearchSession>> future = SettableFuture
69                 .create();
70         ExecutorService executor = Executors.newCachedThreadPool();
71         appSearchManager.createEnterpriseGlobalSearchSession(executor, future::set);
72         return Futures.transformAsync(
73                 future,
74                 instance -> {
75                     if (!instance.isSuccess()) {
76                         return Futures.immediateFailedFuture(
77                                 new AppSearchException(instance.getResultCode(),
78                                         instance.getErrorMessage()));
79                     }
80                     return Futures.immediateFuture(
81                             new EnterpriseGlobalSearchSessionShimImpl(instance.getResultValue(),
82                                     executor));
83                 },
84                 executor);
85     }
86 
87     private EnterpriseGlobalSearchSessionShimImpl(
88             @NonNull EnterpriseGlobalSearchSession session, @NonNull ExecutorService executor) {
89         mEnterpriseGlobalSearchSession = Objects.requireNonNull(session);
90         mExecutor = Objects.requireNonNull(executor);
91     }
92 
93     @NonNull
94     @Override
95     public ListenableFuture<AppSearchBatchResult<String, GenericDocument>> getByDocumentIdAsync(
96             @NonNull String packageName,
97             @NonNull String databaseName,
98             @NonNull GetByDocumentIdRequest request) {
99         SettableFuture<AppSearchBatchResult<String, GenericDocument>> future =
100                 SettableFuture.create();
101         mEnterpriseGlobalSearchSession.getByDocumentId(
102                 packageName, databaseName, request, mExecutor,
103                 new BatchResultCallbackAdapter<>(future));
104         return future;
105     }
106 
107     @NonNull
108     @Override
109     public SearchResultsShim search(
110             @NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
111         SearchResults searchResults = mEnterpriseGlobalSearchSession.search(queryExpression,
112                 searchSpec);
113         return new SearchResultsShimImpl(searchResults, mExecutor);
114     }
115 
116     @NonNull
117     @Override
118     public ListenableFuture<GetSchemaResponse> getSchemaAsync(
119             @NonNull String packageName, @NonNull String databaseName) {
120         SettableFuture<AppSearchResult<GetSchemaResponse>> future = SettableFuture.create();
121         mEnterpriseGlobalSearchSession.getSchema(packageName, databaseName, mExecutor, future::set);
122         return Futures.transformAsync(future, this::transformResult, mExecutor);
123     }
124 
125     @NonNull
126     @Override
127     public Features getFeatures() {
128         return new MainlineFeaturesImpl();
129     }
130 
131     private <T> ListenableFuture<T> transformResult(
132             @NonNull AppSearchResult<T> result) throws AppSearchException {
133         if (!result.isSuccess()) {
134             throw new AppSearchException(result.getResultCode(), result.getErrorMessage());
135         }
136         return Futures.immediateFuture(result.getResultValue());
137     }
138 }
139