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 android.app.appsearch;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.NonNull;
21 import android.app.appsearch.aidl.AppSearchBatchResultParcel;
22 import android.app.appsearch.aidl.AppSearchResultParcel;
23 import android.app.appsearch.aidl.IAppSearchBatchResultCallback;
24 import android.app.appsearch.exceptions.AppSearchException;
25 import android.app.appsearch.safeparcel.GenericDocumentParcel;
26 import android.util.Log;
27 
28 import com.android.internal.util.Preconditions;
29 
30 import java.util.Map;
31 import java.util.Map.Entry;
32 import java.util.concurrent.Executor;
33 import java.util.function.Consumer;
34 
35 /**
36  * Contains util methods used in both {@link GlobalSearchSession} and {@link AppSearchSession}.
37  *
38  * @hide
39  */
40 public class SearchSessionUtil {
41     private static final String TAG = "AppSearchSessionUtil";
42 
43     /** Constructor for in case we create an instance */
SearchSessionUtil()44     private SearchSessionUtil() {}
45 
46     /**
47      * Calls {@link BatchResultCallback#onSystemError} with a throwable derived from the given
48      * failed {@link AppSearchResult}.
49      *
50      * <p>The {@link AppSearchResult} generally comes from {@link
51      * IAppSearchBatchResultCallback#onSystemError}.
52      *
53      * <p>This method should be called from the callback executor thread.
54      *
55      * @param failedResult the error
56      * @param callback the callback to send the error to
57      */
sendSystemErrorToCallback( @onNull AppSearchResult<?> failedResult, @NonNull BatchResultCallback<?, ?> callback)58     public static void sendSystemErrorToCallback(
59             @NonNull AppSearchResult<?> failedResult, @NonNull BatchResultCallback<?, ?> callback) {
60         Preconditions.checkArgument(!failedResult.isSuccess());
61         Throwable throwable =
62                 new AppSearchException(
63                         failedResult.getResultCode(), failedResult.getErrorMessage());
64         callback.onSystemError(throwable);
65     }
66 
67     /**
68      * Safely executes the given lambda on the given executor.
69      *
70      * <p>The {@link Executor#execute} call is wrapped in a try/catch. This prevents situations like
71      * the executor being shut down or the lambda throwing an exception on a direct executor from
72      * crashing the app.
73      *
74      * <p>If execution fails for the above reasons, a failure notification is delivered to
75      * errorCallback synchronously on the calling thread.
76      *
77      * @param executor The executor on which to safely execute the lambda
78      * @param errorCallback The callback to trigger with a failed {@link AppSearchResult} if the
79      *     {@link Executor#execute} call fails.
80      * @param runnable The lambda to execute on the executor
81      */
safeExecute( @onNull Executor executor, @NonNull Consumer<AppSearchResult<T>> errorCallback, @NonNull Runnable runnable)82     public static <T> void safeExecute(
83             @NonNull Executor executor,
84             @NonNull Consumer<AppSearchResult<T>> errorCallback,
85             @NonNull Runnable runnable) {
86         try {
87             executor.execute(runnable);
88         } catch (RuntimeException e) {
89             Log.e(TAG, "Failed to schedule runnable", e);
90             errorCallback.accept(AppSearchResult.throwableToFailedResult(e));
91         }
92     }
93 
94     /**
95      * Safely executes the given lambda on the given executor.
96      *
97      * <p>The {@link Executor#execute} call is wrapped in a try/catch. This prevents situations like
98      * the executor being shut down or the lambda throwing an exception on a direct executor from
99      * crashing the app.
100      *
101      * <p>If execution fails for the above reasons, a failure notification is delivered to
102      * errorCallback synchronously on the calling thread.
103      *
104      * @param executor The executor on which to safely execute the lambda
105      * @param errorCallback The callback to trigger with a failed {@link AppSearchResult} if the
106      *     {@link Executor#execute} call fails.
107      * @param runnable The lambda to execute on the executor
108      */
safeExecute( @onNull Executor executor, @NonNull BatchResultCallback<?, ?> errorCallback, @NonNull Runnable runnable)109     public static void safeExecute(
110             @NonNull Executor executor,
111             @NonNull BatchResultCallback<?, ?> errorCallback,
112             @NonNull Runnable runnable) {
113         try {
114             executor.execute(runnable);
115         } catch (RuntimeException e) {
116             Log.e(TAG, "Failed to schedule runnable", e);
117             errorCallback.onSystemError(e);
118         }
119     }
120 
121     /**
122      * Handler for asynchronous getDocuments method
123      *
124      * @param executor executor to run the callback
125      * @param callback the next method that uses the {@link GenericDocument}
126      * @return A callback to be executed once an {@link AppSearchBatchResultParcel} is received
127      */
createGetDocumentCallback( @onNull @allbackExecutor Executor executor, @NonNull BatchResultCallback<String, GenericDocument> callback)128     public static IAppSearchBatchResultCallback createGetDocumentCallback(
129             @NonNull @CallbackExecutor Executor executor,
130             @NonNull BatchResultCallback<String, GenericDocument> callback) {
131         return new IAppSearchBatchResultCallback.Stub() {
132             @Override
133             @SuppressWarnings({"unchecked", "rawtypes"})
134             public void onResult(AppSearchBatchResultParcel resultParcel) {
135                 safeExecute(
136                         executor,
137                         callback,
138                         () -> {
139                             AppSearchBatchResult<String, GenericDocumentParcel> result =
140                                     resultParcel.getResult();
141                             AppSearchBatchResult.Builder<String, GenericDocument>
142                                     documentResultBuilder = new AppSearchBatchResult.Builder<>();
143 
144                             for (Map.Entry<String, GenericDocumentParcel> entry :
145                                     result.getSuccesses().entrySet()) {
146                                 GenericDocument document;
147                                 try {
148                                     GenericDocumentParcel genericDocumentParcel = entry.getValue();
149                                     if (genericDocumentParcel == null) {
150                                         documentResultBuilder.setFailure(
151                                                 entry.getKey(),
152                                                 AppSearchResult.RESULT_INTERNAL_ERROR,
153                                                 "Received null GenericDocumentParcel in"
154                                                         + " getByDocumentId API");
155                                         continue;
156                                     }
157                                     document = new GenericDocument(genericDocumentParcel);
158                                 } catch (RuntimeException e) {
159                                     documentResultBuilder.setFailure(
160                                             entry.getKey(),
161                                             AppSearchResult.RESULT_INTERNAL_ERROR,
162                                             e.getMessage());
163                                     continue;
164                                 }
165                                 documentResultBuilder.setSuccess(entry.getKey(), document);
166                             }
167 
168                             for (Entry<String, AppSearchResult<GenericDocumentParcel>> entry :
169                                     result.getFailures().entrySet()) {
170                                 documentResultBuilder.setFailure(
171                                         entry.getKey(),
172                                         entry.getValue().getResultCode(),
173                                         entry.getValue().getErrorMessage());
174                             }
175                             callback.onResult(documentResultBuilder.build());
176                         });
177             }
178 
179             @Override
180             @SuppressWarnings({"unchecked", "rawtypes"})
181             public void onSystemError(AppSearchResultParcel result) {
182                 safeExecute(
183                         executor,
184                         callback,
185                         () -> sendSystemErrorToCallback(result.getResult(), callback));
186             }
187         };
188     }
189 }
190