diff options
| author | 2024-09-09 10:28:46 +0000 | |
|---|---|---|
| committer | 2024-09-09 10:28:46 +0000 | |
| commit | efde44764cd00b995a42242e6e50eb7002b898b5 (patch) | |
| tree | 6b6427f4238b23d8cdb537e5f8a3424951963979 | |
| parent | 29d12d84bd5513303f0fa837dc3ad8650345c608 (diff) | |
| parent | bca640f6ef969ae460390ff7993181a988169a18 (diff) | |
Merge "Introduce FutureGlobalSearchSession" into main
6 files changed, 266 insertions, 39 deletions
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java index eba628dc1fba..03dd5dd62e51 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java +++ b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java @@ -16,9 +16,6 @@ package com.android.server.appfunctions; -import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER; - -import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.app.appsearch.AppSearchBatchResult; import android.app.appsearch.AppSearchManager; @@ -42,10 +39,7 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; -/** - * A future API wrapper of {@link AppSearchSession} APIs. - */ -@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER) +/** A future API wrapper of {@link AppSearchSession} APIs. */ public class FutureAppSearchSession implements Closeable { private static final String TAG = FutureAppSearchSession.class.getSimpleName(); private final Executor mExecutor; @@ -67,14 +61,14 @@ public class FutureAppSearchSession implements Closeable { /** Converts a failed app search result codes into an exception. */ @NonNull - private static Exception failedResultToException(@NonNull AppSearchResult<?> appSearchResult) { + public static Exception failedResultToException(@NonNull AppSearchResult<?> appSearchResult) { return switch (appSearchResult.getResultCode()) { - case AppSearchResult.RESULT_INVALID_ARGUMENT -> new IllegalArgumentException( - appSearchResult.getErrorMessage()); - case AppSearchResult.RESULT_IO_ERROR -> new IOException( - appSearchResult.getErrorMessage()); - case AppSearchResult.RESULT_SECURITY_ERROR -> new SecurityException( - appSearchResult.getErrorMessage()); + case AppSearchResult.RESULT_INVALID_ARGUMENT -> + new IllegalArgumentException(appSearchResult.getErrorMessage()); + case AppSearchResult.RESULT_IO_ERROR -> + new IOException(appSearchResult.getErrorMessage()); + case AppSearchResult.RESULT_SECURITY_ERROR -> + new SecurityException(appSearchResult.getErrorMessage()); default -> new IllegalStateException(appSearchResult.getErrorMessage()); }; } @@ -137,14 +131,16 @@ public class FutureAppSearchSession implements Closeable { /** Indexes documents into the AppSearchSession database. */ public AndroidFuture<AppSearchBatchResult<String, Void>> put( @NonNull PutDocumentsRequest putDocumentsRequest) { - return getSessionAsync().thenCompose( - session -> { - AndroidFuture<AppSearchBatchResult<String, Void>> batchResultFuture = - new AndroidFuture<>(); + return getSessionAsync() + .thenCompose( + session -> { + AndroidFuture<AppSearchBatchResult<String, Void>> batchResultFuture = + new AndroidFuture<>(); - session.put(putDocumentsRequest, mExecutor, batchResultFuture::complete); - return batchResultFuture; - }); + session.put( + putDocumentsRequest, mExecutor, batchResultFuture::complete); + return batchResultFuture; + }); } /** @@ -152,10 +148,9 @@ public class FutureAppSearchSession implements Closeable { * of search provided. */ public AndroidFuture<FutureSearchResults> search( - @NonNull String queryExpression, - @NonNull SearchSpec searchSpec) { - return getSessionAsync().thenApply( - session -> session.search(queryExpression, searchSpec)) + @NonNull String queryExpression, @NonNull SearchSpec searchSpec) { + return getSessionAsync() + .thenApply(session -> session.search(queryExpression, searchSpec)) .thenApply(result -> new FutureSearchResults(result, mExecutor)); } @@ -173,8 +168,8 @@ public class FutureAppSearchSession implements Closeable { private final SearchResults mSearchResults; private final Executor mExecutor; - public FutureSearchResults(@NonNull SearchResults searchResults, - @NonNull Executor executor) { + public FutureSearchResults( + @NonNull SearchResults searchResults, @NonNull Executor executor) { mSearchResults = Objects.requireNonNull(searchResults); mExecutor = Objects.requireNonNull(executor); } @@ -184,15 +179,14 @@ public class FutureAppSearchSession implements Closeable { new AndroidFuture<>(); mSearchResults.getNextPage(mExecutor, nextPageFuture::complete); - return nextPageFuture.thenApply(result -> { - if (result.isSuccess()) { - return result.getResultValue(); - } else { - throw new RuntimeException( - failedResultToException(result)); - } - }); + return nextPageFuture.thenApply( + result -> { + if (result.isSuccess()) { + return result.getResultValue(); + } else { + throw new RuntimeException(failedResultToException(result)); + } + }); } - } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java b/services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java new file mode 100644 index 000000000000..0c2262456032 --- /dev/null +++ b/services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.appfunctions; + +import android.annotation.NonNull; +import android.app.appsearch.AppSearchManager; +import android.app.appsearch.AppSearchResult; +import android.app.appsearch.GlobalSearchSession; +import android.app.appsearch.exceptions.AppSearchException; +import android.app.appsearch.observer.ObserverCallback; +import android.app.appsearch.observer.ObserverSpec; +import android.util.Slog; + +import com.android.internal.infra.AndroidFuture; + +import java.io.Closeable; +import java.io.IOException; +import java.util.concurrent.Executor; + +/** A wrapper around {@link GlobalSearchSession} that provides a future-based API. */ +public class FutureGlobalSearchSession implements Closeable { + private static final String TAG = FutureGlobalSearchSession.class.getSimpleName(); + private final Executor mExecutor; + private final AndroidFuture<AppSearchResult<GlobalSearchSession>> mSettableSessionFuture; + + public FutureGlobalSearchSession( + @NonNull AppSearchManager appSearchManager, @NonNull Executor executor) { + this.mExecutor = executor; + mSettableSessionFuture = new AndroidFuture<>(); + appSearchManager.createGlobalSearchSession(mExecutor, mSettableSessionFuture::complete); + } + + private AndroidFuture<GlobalSearchSession> getSessionAsync() { + return mSettableSessionFuture.thenApply( + result -> { + if (result.isSuccess()) { + return result.getResultValue(); + } else { + throw new RuntimeException( + FutureAppSearchSession.failedResultToException(result)); + } + }); + } + + /** + * Registers an observer callback for the given target package name. + * + * @param targetPackageName The package name of the target app. + * @param spec The observer spec. + * @param executor The executor to run the observer callback on. + * @param observer The observer callback to register. + * @return A future that completes once the observer is registered. + */ + public AndroidFuture<Void> registerObserverCallbackAsync( + String targetPackageName, + ObserverSpec spec, + Executor executor, + ObserverCallback observer) { + return getSessionAsync() + .thenCompose( + session -> { + try { + session.registerObserverCallback( + targetPackageName, spec, executor, observer); + return AndroidFuture.completedFuture(null); + } catch (AppSearchException e) { + throw new RuntimeException(e); + } + }); + } + + @Override + public void close() throws IOException { + try { + getSessionAsync().get().close(); + } catch (Exception ex) { + Slog.e(TAG, "Failed to close global search session", ex); + } + } +} diff --git a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java index 98903ae57a39..58597c38bb94 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java +++ b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCaller.java @@ -25,7 +25,6 @@ import android.os.UserHandle; * services are properly unbound after the operation completes or a timeout occurs. * * @param <T> Class of wrapped service. - * @hide */ public interface RemoteServiceCaller<T> { diff --git a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java index 0e18705c40b0..eea17eeca371 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java @@ -34,7 +34,6 @@ import java.util.function.Function; * Context#bindService}. * * @param <T> Class of wrapped service. - * @hide */ public class RemoteServiceCallerImpl<T> implements RemoteServiceCaller<T> { private static final String TAG = "AppFunctionsServiceCall"; diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt index 5233f194d6c5..a323799e85e5 100644 --- a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt +++ b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureAppSearchSessionTest.kt @@ -42,7 +42,7 @@ class FutureAppSearchSessionTest { fun clearData() { val searchContext = AppSearchManager.SearchContext.Builder(TEST_DB).build() FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use { - val setSchemaRequest = SetSchemaRequest.Builder().build() + val setSchemaRequest = SetSchemaRequest.Builder().setForceOverride(true).build() it.setSchema(setSchemaRequest) } } diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/FutureGlobalSearchSessionTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureGlobalSearchSessionTest.kt new file mode 100644 index 000000000000..8817a66c734e --- /dev/null +++ b/services/tests/appfunctions/src/com/android/server/appfunctions/FutureGlobalSearchSessionTest.kt @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.appfunctions + +import android.app.appfunctions.AppFunctionRuntimeMetadata +import android.app.appfunctions.AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema +import android.app.appfunctions.AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema +import android.app.appfunctions.AppFunctionStaticMetadataHelper +import android.app.appsearch.AppSearchManager +import android.app.appsearch.AppSearchManager.SearchContext +import android.app.appsearch.PutDocumentsRequest +import android.app.appsearch.SetSchemaRequest +import android.app.appsearch.observer.DocumentChangeInfo +import android.app.appsearch.observer.ObserverCallback +import android.app.appsearch.observer.ObserverSpec +import android.app.appsearch.observer.SchemaChangeInfo +import androidx.test.platform.app.InstrumentationRegistry +import com.android.internal.infra.AndroidFuture +import com.google.common.truth.Truth.assertThat +import com.google.common.util.concurrent.MoreExecutors +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 + +@RunWith(JUnit4::class) +class FutureGlobalSearchSessionTest { + private val context = InstrumentationRegistry.getInstrumentation().targetContext + private val appSearchManager = context.getSystemService(AppSearchManager::class.java) + private val testExecutor = MoreExecutors.directExecutor() + + @Before + @After + fun clearData() { + val searchContext = SearchContext.Builder(TEST_DB).build() + FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use { + val setSchemaRequest = SetSchemaRequest.Builder().setForceOverride(true).build() + it.setSchema(setSchemaRequest) + } + } + + @Test + fun registerDocumentChangeObserverCallback() { + val baseObserverSpec: ObserverSpec = + ObserverSpec.Builder() + .addFilterSchemas(AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE) + .build() + val packageObserverSpec: ObserverSpec = + ObserverSpec.Builder() + .addFilterSchemas( + AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage(TEST_TARGET_PKG_NAME) + ) + .build() + val settableDocumentChangeInfo: AndroidFuture<DocumentChangeInfo> = AndroidFuture() + val observer: ObserverCallback = + object : ObserverCallback { + override fun onSchemaChanged(changeInfo: SchemaChangeInfo) {} + + override fun onDocumentChanged(changeInfo: DocumentChangeInfo) { + settableDocumentChangeInfo.complete(changeInfo) + } + } + val futureGlobalSearchSession = FutureGlobalSearchSession(appSearchManager, testExecutor) + + val registerBaseObserver: Void? = + futureGlobalSearchSession + .registerObserverCallbackAsync( + TEST_TARGET_PKG_NAME, + baseObserverSpec, + testExecutor, + observer, + ) + .get() + val registerPackageObserver: Void? = + futureGlobalSearchSession + .registerObserverCallbackAsync( + TEST_TARGET_PKG_NAME, + packageObserverSpec, + testExecutor, + observer, + ) + .get() + + assertThat(registerBaseObserver).isNull() + assertThat(registerPackageObserver).isNull() + // Trigger document change + val searchContext = SearchContext.Builder(TEST_DB).build() + FutureAppSearchSession(appSearchManager, testExecutor, searchContext).use { session -> + val setSchemaRequest = + SetSchemaRequest.Builder() + .addSchemas( + createParentAppFunctionRuntimeSchema(), + createAppFunctionRuntimeSchema(TEST_TARGET_PKG_NAME), + ) + .build() + val schema = session.setSchema(setSchemaRequest) + assertThat(schema.get()).isNotNull() + val appFunctionRuntimeMetadata = + AppFunctionRuntimeMetadata.Builder(TEST_TARGET_PKG_NAME, TEST_FUNCTION_ID, "") + .build() + val putDocumentsRequest: PutDocumentsRequest = + PutDocumentsRequest.Builder() + .addGenericDocuments(appFunctionRuntimeMetadata) + .build() + val putResult = session.put(putDocumentsRequest).get() + assertThat(putResult.isSuccess).isTrue() + } + assertThat( + settableDocumentChangeInfo + .get() + .changedDocumentIds + .contains( + AppFunctionRuntimeMetadata.getDocumentIdForAppFunction( + TEST_TARGET_PKG_NAME, + TEST_FUNCTION_ID, + ) + ) + ) + .isTrue() + } + + private companion object { + const val TEST_DB: String = "test_db" + const val TEST_TARGET_PKG_NAME = "com.android.frameworks.appfunctionstests" + const val TEST_FUNCTION_ID: String = "print" + } +} |