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