Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6dd2c707 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[MetadataSyncAdapter Sync api] - Add api for submitting a sync request...

Merge "[MetadataSyncAdapter Sync api] - Add api for submitting a sync request to the sync executor." into main
parents 02d33275 881c12ec
Loading
Loading
Loading
Loading
+10 −27
Original line number Diff line number Diff line
@@ -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;
    interface FutureSearchResults {

        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));
                        }
                    });
        }
        /**
         * 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();
    }
}
+31 −1
Original line number Diff line number Diff line
@@ -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;
+239 −1
Original line number Diff line number Diff line
@@ -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();
    }
}
+199 −0

File changed.

Preview size limit exceeded, changes collapsed.