summaryrefslogtreecommitdiff
path: root/services/appfunctions/java
diff options
context:
space:
mode:
author Treehugger Robot <android-test-infra-autosubmit@system.gserviceaccount.com> 2024-09-12 15:09:14 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-09-12 15:09:14 +0000
commit6dd2c7074979991187378907077cf9141c0604a8 (patch)
tree3771796eccbd410e5113b798e70ddadf75408fff /services/appfunctions/java
parent02d33275c990cbe7feccf82f7815f3da455ef04b (diff)
parent881c12ec5bcf3a5e7463e16dae1d0d5baefd2d28 (diff)
Merge "[MetadataSyncAdapter Sync api] - Add api for submitting a sync request to the sync executor." into main
Diffstat (limited to 'services/appfunctions/java')
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java37
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java32
-rw-r--r--services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java240
3 files changed, 280 insertions, 29 deletions
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
index b1c25c4f3c61..56f373d22f75 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java
@@ -26,7 +26,6 @@ import android.app.appsearch.GetSchemaResponse;
import android.app.appsearch.PutDocumentsRequest;
import android.app.appsearch.RemoveByDocumentIdRequest;
import android.app.appsearch.SearchResult;
-import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaRequest;
import android.app.appsearch.SetSchemaResponse;
@@ -36,8 +35,6 @@ import com.android.internal.infra.AndroidFuture;
import java.io.Closeable;
import java.io.IOException;
import java.util.List;
-import java.util.Objects;
-import java.util.concurrent.Executor;
/** A future API wrapper of {@link AppSearchSession} APIs. */
public interface FutureAppSearchSession extends Closeable {
@@ -88,29 +85,15 @@ public interface FutureAppSearchSession extends Closeable {
@NonNull String queryExpression, @NonNull SearchSpec searchSpec);
/** A future API wrapper of {@link android.app.appsearch.SearchResults}. */
- class FutureSearchResults {
- private final SearchResults mSearchResults;
- private final Executor mExecutor;
-
- public FutureSearchResults(
- @NonNull SearchResults searchResults, @NonNull Executor executor) {
- mSearchResults = Objects.requireNonNull(searchResults);
- mExecutor = Objects.requireNonNull(executor);
- }
-
- public AndroidFuture<List<SearchResult>> getNextPage() {
- AndroidFuture<AppSearchResult<List<SearchResult>>> nextPageFuture =
- new AndroidFuture<>();
-
- mSearchResults.getNextPage(mExecutor, nextPageFuture::complete);
- return nextPageFuture.thenApply(
- result -> {
- if (result.isSuccess()) {
- return result.getResultValue();
- } else {
- throw new RuntimeException(failedResultToException(result));
- }
- });
- }
+ interface FutureSearchResults {
+
+ /**
+ * Retrieves the next page of {@link SearchResult} objects from the {@link AppSearchSession}
+ * database.
+ *
+ * <p>Continue calling this method to access results until it returns an empty list,
+ * signifying there are no more results.
+ */
+ AndroidFuture<List<SearchResult>> getNextPage();
}
}
diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java
index e78f390a4825..3079d9f51bf3 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java
@@ -30,6 +30,8 @@ import android.app.appsearch.GetByDocumentIdRequest;
import android.app.appsearch.GetSchemaResponse;
import android.app.appsearch.PutDocumentsRequest;
import android.app.appsearch.RemoveByDocumentIdRequest;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.SearchResults;
import android.app.appsearch.SearchSpec;
import android.app.appsearch.SetSchemaRequest;
import android.app.appsearch.SetSchemaResponse;
@@ -37,6 +39,7 @@ import android.app.appsearch.SetSchemaResponse;
import com.android.internal.infra.AndroidFuture;
import java.io.IOException;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -176,12 +179,39 @@ public class FutureAppSearchSessionImpl implements FutureAppSearchSession {
@NonNull String queryExpression, @NonNull SearchSpec searchSpec) {
return getSessionAsync()
.thenApply(session -> session.search(queryExpression, searchSpec))
- .thenApply(result -> new FutureSearchResults(result, mExecutor));
+ .thenApply(result -> new FutureSearchResultsImpl(result, mExecutor));
}
@Override
public void close() throws IOException {}
+ private static final class FutureSearchResultsImpl implements FutureSearchResults {
+ private final SearchResults mSearchResults;
+ private final Executor mExecutor;
+
+ private FutureSearchResultsImpl(
+ @NonNull SearchResults searchResults, @NonNull Executor executor) {
+ this.mSearchResults = searchResults;
+ this.mExecutor = executor;
+ }
+
+ @Override
+ public AndroidFuture<List<SearchResult>> getNextPage() {
+ AndroidFuture<AppSearchResult<List<SearchResult>>> nextPageFuture =
+ new AndroidFuture<>();
+
+ mSearchResults.getNextPage(mExecutor, nextPageFuture::complete);
+ return nextPageFuture.thenApply(
+ result -> {
+ if (result.isSuccess()) {
+ return result.getResultValue();
+ } else {
+ throw new RuntimeException(failedResultToException(result));
+ }
+ });
+ }
+ }
+
private static final class BatchResultCallbackAdapter<K, V>
implements BatchResultCallback<K, V> {
private final AndroidFuture<AppSearchBatchResult<K, V>> mFuture;
diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
index 01e10086a03a..5f608043927b 100644
--- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
+++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java
@@ -16,35 +16,238 @@
package com.android.server.appfunctions;
+import static android.app.appfunctions.AppFunctionRuntimeMetadata.RUNTIME_SCHEMA_TYPE;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.WorkerThread;
+import android.app.appfunctions.AppFunctionRuntimeMetadata;
+import android.app.appfunctions.AppFunctionStaticMetadataHelper;
+import android.app.appsearch.AppSearchBatchResult;
+import android.app.appsearch.AppSearchResult;
+import android.app.appsearch.AppSearchSchema;
+import android.app.appsearch.PackageIdentifier;
import android.app.appsearch.PropertyPath;
+import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.RemoveByDocumentIdRequest;
import android.app.appsearch.SearchResult;
import android.app.appsearch.SearchSpec;
+import android.app.appsearch.SetSchemaRequest;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.infra.AndroidFuture;
import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Collection;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
/**
* This class implements helper methods for synchronously interacting with AppSearch while
* synchronizing AppFunction runtime and static metadata.
+ *
+ * <p>This class is not thread safe.
*/
public class MetadataSyncAdapter {
+ private static final String TAG = MetadataSyncAdapter.class.getSimpleName();
private final FutureAppSearchSession mFutureAppSearchSession;
private final Executor mSyncExecutor;
+ private final PackageManager mPackageManager;
+
+ // Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility
+ // by permissions.
+ public static final int EXECUTE_APP_FUNCTIONS = 9;
+ public static final int EXECUTE_APP_FUNCTIONS_TRUSTED = 10;
public MetadataSyncAdapter(
@NonNull Executor syncExecutor,
- @NonNull FutureAppSearchSession futureAppSearchSession) {
+ @NonNull FutureAppSearchSession futureAppSearchSession,
+ @NonNull PackageManager packageManager) {
mSyncExecutor = Objects.requireNonNull(syncExecutor);
mFutureAppSearchSession = Objects.requireNonNull(futureAppSearchSession);
+ mPackageManager = Objects.requireNonNull(packageManager);
+ }
+
+ /**
+ * This method submits a request to synchronize the AppFunction runtime and static metadata.
+ *
+ * @return A {@link AndroidFuture} that completes with a boolean value indicating whether the
+ * synchronization was successful.
+ */
+ public AndroidFuture<Boolean> submitSyncRequest() {
+ AndroidFuture<Boolean> settableSyncStatus = new AndroidFuture<>();
+ mSyncExecutor.execute(
+ () -> {
+ try {
+ trySyncAppFunctionMetadataBlocking();
+ settableSyncStatus.complete(true);
+ } catch (Exception e) {
+ settableSyncStatus.completeExceptionally(e);
+ }
+ });
+ return settableSyncStatus;
+ }
+
+ @WorkerThread
+ private void trySyncAppFunctionMetadataBlocking()
+ throws ExecutionException, InterruptedException {
+ ArrayMap<String, ArraySet<String>> staticPackageToFunctionMap =
+ getPackageToFunctionIdMap(
+ AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE,
+ AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID,
+ AppFunctionStaticMetadataHelper.PROPERTY_PACKAGE_NAME);
+ ArrayMap<String, ArraySet<String>> runtimePackageToFunctionMap =
+ getPackageToFunctionIdMap(
+ RUNTIME_SCHEMA_TYPE,
+ AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID,
+ AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME);
+
+ ArrayMap<String, ArraySet<String>> addedFunctionsDiffMap =
+ getAddedFunctionsDiffMap(staticPackageToFunctionMap, runtimePackageToFunctionMap);
+ ArrayMap<String, ArraySet<String>> removedFunctionsDiffMap =
+ getRemovedFunctionsDiffMap(staticPackageToFunctionMap, runtimePackageToFunctionMap);
+
+ Set<AppSearchSchema> appRuntimeMetadataSchemas =
+ getAllRuntimeMetadataSchemas(staticPackageToFunctionMap.keySet());
+ appRuntimeMetadataSchemas.add(
+ AppFunctionRuntimeMetadata.createParentAppFunctionRuntimeSchema());
+
+ // Operation order matters here. i.e. remove -> setSchema -> add. Otherwise we would
+ // encounter an error trying to delete a document with no existing schema.
+ if (!removedFunctionsDiffMap.isEmpty()) {
+ RemoveByDocumentIdRequest removeByDocumentIdRequest =
+ buildRemoveRuntimeMetadataRequest(removedFunctionsDiffMap);
+ AppSearchBatchResult<String, Void> removeDocumentBatchResult =
+ mFutureAppSearchSession.remove(removeByDocumentIdRequest).get();
+ if (!removeDocumentBatchResult.isSuccess()) {
+ throw convertFailedAppSearchResultToException(
+ removeDocumentBatchResult.getFailures().values());
+ }
+ }
+
+ if (!addedFunctionsDiffMap.isEmpty()) {
+ // TODO(b/357551503): only set schema on package diff
+ SetSchemaRequest addSetSchemaRequest =
+ buildSetSchemaRequestForRuntimeMetadataSchemas(appRuntimeMetadataSchemas);
+ Objects.requireNonNull(mFutureAppSearchSession.setSchema(addSetSchemaRequest).get());
+ PutDocumentsRequest putDocumentsRequest =
+ buildPutRuntimeMetadataRequest(addedFunctionsDiffMap);
+ AppSearchBatchResult<String, Void> putDocumentBatchResult =
+ mFutureAppSearchSession.put(putDocumentsRequest).get();
+ if (!putDocumentBatchResult.isSuccess()) {
+ throw convertFailedAppSearchResultToException(
+ putDocumentBatchResult.getFailures().values());
+ }
+ }
+ }
+
+ @NonNull
+ private static IllegalStateException convertFailedAppSearchResultToException(
+ @NonNull Collection<AppSearchResult<Void>> appSearchResult) {
+ Objects.requireNonNull(appSearchResult);
+ StringBuilder errorMessages = new StringBuilder();
+ for (AppSearchResult<Void> result : appSearchResult) {
+ errorMessages.append(result.getErrorMessage());
+ }
+ return new IllegalStateException(errorMessages.toString());
+ }
+
+ @NonNull
+ private PutDocumentsRequest buildPutRuntimeMetadataRequest(
+ @NonNull ArrayMap<String, ArraySet<String>> addedFunctionsDiffMap) {
+ Objects.requireNonNull(addedFunctionsDiffMap);
+ PutDocumentsRequest.Builder putDocumentRequestBuilder = new PutDocumentsRequest.Builder();
+
+ for (int i = 0; i < addedFunctionsDiffMap.size(); i++) {
+ String packageName = addedFunctionsDiffMap.keyAt(i);
+ ArraySet<String> addedFunctionIds = addedFunctionsDiffMap.valueAt(i);
+ for (String addedFunctionId : addedFunctionIds) {
+ putDocumentRequestBuilder.addGenericDocuments(
+ new AppFunctionRuntimeMetadata.Builder(
+ packageName,
+ addedFunctionId,
+ AppFunctionRuntimeMetadata
+ .PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID)
+ .build());
+ }
+ }
+ return putDocumentRequestBuilder.build();
+ }
+
+ @NonNull
+ private RemoveByDocumentIdRequest buildRemoveRuntimeMetadataRequest(
+ @NonNull ArrayMap<String, ArraySet<String>> removedFunctionsDiffMap) {
+ Objects.requireNonNull(AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_NAMESPACE);
+ Objects.requireNonNull(removedFunctionsDiffMap);
+ RemoveByDocumentIdRequest.Builder removeDocumentRequestBuilder =
+ new RemoveByDocumentIdRequest.Builder(
+ AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_NAMESPACE);
+
+ for (int i = 0; i < removedFunctionsDiffMap.size(); i++) {
+ String packageName = removedFunctionsDiffMap.keyAt(i);
+ ArraySet<String> removedFunctionIds = removedFunctionsDiffMap.valueAt(i);
+ for (String functionId : removedFunctionIds) {
+ String documentId =
+ AppFunctionRuntimeMetadata.getDocumentIdForAppFunction(
+ packageName, functionId);
+ removeDocumentRequestBuilder.addIds(documentId);
+ }
+ }
+ return removeDocumentRequestBuilder.build();
+ }
+
+ @NonNull
+ private SetSchemaRequest buildSetSchemaRequestForRuntimeMetadataSchemas(
+ @NonNull Set<AppSearchSchema> metadataSchemaSet) {
+ Objects.requireNonNull(metadataSchemaSet);
+ SetSchemaRequest.Builder setSchemaRequestBuilder =
+ new SetSchemaRequest.Builder().setForceOverride(true).addSchemas(metadataSchemaSet);
+
+ for (AppSearchSchema runtimeMetadataSchema : metadataSchemaSet) {
+ String packageName =
+ AppFunctionRuntimeMetadata.getPackageNameFromSchema(
+ runtimeMetadataSchema.getSchemaType());
+ byte[] packageCert = getCertificate(packageName);
+ if (packageCert == null) {
+ continue;
+ }
+ setSchemaRequestBuilder.setSchemaTypeVisibilityForPackage(
+ runtimeMetadataSchema.getSchemaType(),
+ true,
+ new PackageIdentifier(packageName, packageCert));
+ }
+
+ setSchemaRequestBuilder.addRequiredPermissionsForSchemaTypeVisibility(
+ RUNTIME_SCHEMA_TYPE, Set.of(EXECUTE_APP_FUNCTIONS));
+ setSchemaRequestBuilder.addRequiredPermissionsForSchemaTypeVisibility(
+ RUNTIME_SCHEMA_TYPE, Set.of(EXECUTE_APP_FUNCTIONS_TRUSTED));
+ return setSchemaRequestBuilder.build();
+ }
+
+ @NonNull
+ @WorkerThread
+ private Set<AppSearchSchema> getAllRuntimeMetadataSchemas(
+ @NonNull Set<String> staticMetadataPackages) {
+ Objects.requireNonNull(staticMetadataPackages);
+
+ Set<AppSearchSchema> appRuntimeMetadataSchemas = new ArraySet<>();
+ for (String packageName : staticMetadataPackages) {
+ appRuntimeMetadataSchemas.add(
+ AppFunctionRuntimeMetadata.createAppFunctionRuntimeSchema(packageName));
+ }
+
+ return appRuntimeMetadataSchemas;
}
/**
@@ -188,4 +391,39 @@ public class MetadataSyncAdapter {
new PropertyPath(propertyPackageName)))
.build();
}
+
+ /** Gets the SHA-256 certificate from a {@link PackageManager}, or null if it is not found. */
+ @Nullable
+ private byte[] getCertificate(@NonNull String packageName) {
+ Objects.requireNonNull(packageName);
+ PackageInfo packageInfo;
+ try {
+ packageInfo =
+ Objects.requireNonNull(
+ mPackageManager.getPackageInfo(
+ packageName,
+ PackageManager.GET_META_DATA
+ | PackageManager.GET_SIGNING_CERTIFICATES));
+ } catch (Exception e) {
+ Slog.d(TAG, "Package name info not found for package: " + packageName);
+ return null;
+ }
+ if (packageInfo.signingInfo == null) {
+ Slog.d(TAG, "Signing info not found for package: " + packageInfo.packageName);
+ return null;
+ }
+
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance("SHA256");
+ } catch (NoSuchAlgorithmException e) {
+ return null;
+ }
+ Signature[] signatures = packageInfo.signingInfo.getSigningCertificateHistory();
+ if (signatures == null || signatures.length == 0) {
+ return null;
+ }
+ md.update(signatures[0].toByteArray());
+ return md.digest();
+ }
}